1/** 2 * AJAX Nette Framework plugin for jQuery 3 * 4 * @copyright Copyright (c) 2009, 2010 Jan Marek 5 * @copyright Copyright (c) 2009, 2010 David Grudl 6 * @copyright Copyright (c) 2012 VojtÄch DobeÅ¡ 7 * @license MIT 8 * 9 * @version 1.2.2 10 */ 11 12(function(window, $, undefined) { 13 14if (typeof $ != 'function') { 15 return console.error('nette.ajax.js: jQuery is missing, load it please'); 16} 17 18var nette = function () { 19 var inner = { 20 self: this, 21 initialized: false, 22 contexts: {}, 23 on: { 24 init: {}, 25 load: {}, 26 prepare: {}, 27 before: {}, 28 start: {}, 29 success: {}, 30 complete: {}, 31 error: {} 32 }, 33 fire: function () { 34 var result = true; 35 var args = Array.prototype.slice.call(arguments); 36 var props = args.shift(); 37 var name = (typeof props == 'string') ? props : props.name; 38 var off = (typeof props == 'object') ? props.off || {} : {}; 39 args.push(inner.self); 40 $.each(inner.on[name], function (index, reaction) { 41 if (reaction === undefined || $.inArray(index, off) !== -1) return true; 42 var temp = reaction.apply(inner.contexts[index], args); 43 return result = (temp === undefined || temp); 44 }); 45 return result; 46 }, 47 requestHandler: function (e) { 48 if (!inner.self.ajax({}, this, e)) return; 49 }, 50 ext: function (callbacks, context, name) { 51 while (!name) { 52 name = 'ext_' + Math.random(); 53 if (inner.contexts[name]) { 54 name = undefined; 55 } 56 } 57 58 $.each(callbacks, function (event, callback) { 59 inner.on[event][name] = callback; 60 }); 61 inner.contexts[name] = $.extend(context ? context : {}, { 62 name: function () { 63 return name; 64 }, 65 ext: function (name, force) { 66 var ext = inner.contexts[name]; 67 if (!ext && force) throw "Extension '" + this.name() + "' depends on disabled extension '" + name + "'."; 68 return ext; 69 } 70 }); 71 } 72 }; 73 74 /** 75 * Allows manipulation with extensions. 76 * When called with 1. argument only, it returns extension with given name. 77 * When called with 2. argument equal to false, it removes extension entirely. 78 * When called with 2. argument equal to hash of event callbacks, it adds new extension. 79 * 80 * @param {string} Name of extension 81 * @param {bool|object|null} Set of callbacks for any events OR false for removing extension. 82 * @param {object|null} Context for added extension 83 * @return {$.nette|object} Provides a fluent interface OR returns extensions with given name 84 */ 85 this.ext = function (name, callbacks, context) { 86 if (typeof name == 'object') { 87 inner.ext(name, callbacks); 88 } else if (callbacks === undefined) { 89 return inner.contexts[name]; 90 } else if (!callbacks) { 91 $.each(['init', 'load', 'prepare', 'before', 'start', 'success', 'complete', 'error'], function (index, event) { 92 inner.on[event][name] = undefined; 93 }); 94 inner.contexts[name] = undefined; 95 } else if (typeof name == 'string' && inner.contexts[name] !== undefined) { 96 throw "Cannot override already registered nette-ajax extension '" + name + "'."; 97 } else { 98 inner.ext(callbacks, context, name); 99 } 100 return this; 101 }; 102 103 /** 104 * Initializes the plugin: 105 * - fires 'init' event, then 'load' event 106 * - when called with any arguments, it will override default 'init' extension 107 * with provided callbacks 108 * 109 * @param {function|object|null} Callback for 'load' event or entire set of callbacks for any events 110 * @param {object|null} Context provided for callbacks in first argument 111 * @return {$.nette} Provides a fluent interface 112 */ 113 this.init = function (load, loadContext) { 114 if (inner.initialized) throw 'Cannot initialize nette-ajax twice.'; 115 116 if (typeof load == 'function') { 117 this.ext('init', null); 118 this.ext('init', { 119 load: load 120 }, loadContext); 121 } else if (typeof load == 'object') { 122 this.ext('init', null); 123 this.ext('init', load, loadContext); 124 } else if (load !== undefined) { 125 throw 'Argument of init() can be function or function-hash only.'; 126 } 127 128 inner.initialized = true; 129 130 inner.fire('init'); 131 this.load(); 132 return this; 133 }; 134 135 /** 136 * Fires 'load' event 137 * 138 * @return {$.nette} Provides a fluent interface 139 */ 140 this.load = function () { 141 inner.fire('load', inner.requestHandler); 142 return this; 143 }; 144 145 /** 146 * Executes AJAX request. Attaches listeners and events. 147 * 148 * @param {object} settings 149 * @param {Element|null} ussually Anchor or Form 150 * @param {event|null} event causing the request 151 * @return {jqXHR|null} 152 */ 153 this.ajax = function (settings, ui, e) { 154 if (!settings.nette && ui && e) { 155 var $el = $(ui), xhr, originalBeforeSend; 156 var analyze = settings.nette = { 157 e: e, 158 ui: ui, 159 el: $el, 160 isForm: $el.is('form'), 161 isSubmit: $el.is('input[type=submit]') || $el.is('button[type=submit]'), 162 isImage: $el.is('input[type=image]'), 163 form: null 164 }; 165
166 if (analyze.isSubmit || analyze.isImage) { 167 analyze.form = analyze.el.closest('form'); 168 } else if (analyze.isForm) { 169 analyze.form = analyze.el; 170 } 171 172 if (!settings.url) { 173 settings.url = analyze.form ? analyze.form.attr('action') : ui.href; 174 } 175 if (!settings.type) { 176 settings.type = analyze.form ? analyze.form.attr('method') : 'get'; 177 } 178 179 if ($el.is('[data-ajax-off]')) { 180 settings.off = $el.data('ajaxOff'); 181 if (typeof settings.off == 'string') settings.off = [settings.off]; 182 } 183 } 184 185 inner.fire({ 186 name: 'prepare', 187 off: settings.off || {} 188 }, settings); 189 if (settings.prepare) { 190 settings.prepare(settings); 191 } 192 193 originalBeforeSend = settings.beforeSend; 194 settings.beforeSend = function (xhr, settings) { 195 if (originalBeforeSend) { 196 var result = originalBeforeSend(xhr, settings); 197 if (result !== undefined && !result) return result; 198 } 199 return inner.fire({ 200 name: 'before', 201 off: settings.off || {} 202 }, xhr, settings); 203 }; 204 205 xhr = $.ajax(settings); 206 207 if (xhr && (typeof xhr.statusText === 'undefined' || xhr.statusText !== 'canceled')) { 208 xhr.done(function (payload, status, xhr) { 209 inner.fire({ 210 name: 'success', 211 off: settings.off || {} 212 }, payload, status, xhr); 213 }).fail(function (xhr, status, error) { 214 inner.fire({ 215 name: 'error', 216 off: settings.off || {} 217 }, xhr, status, error); 218 }).always(function (xhr, status) { 219 inner.fire({ 220 name: 'complete', 221 off: settings.off || {} 222 }, xhr, status); 223 }); 224 inner.fire({ 225 name: 'start', 226 off: settings.off || {} 227 }, xhr, settings); 228 if (settings.start) { 229 settings.start(xhr, settings); 230 } 231 } 232 return xhr; 233 }; 234}; 235 236$.nette = new ($.extend(nette, $.nette ? $.nette : {})); 237 238$.fn.netteAjax = function (e, options) { 239 return $.nette.ajax(options || {}, this[0], e); 240}; 241 242$.fn.netteAjaxOff = function () { 243 return this.off('.nette'); 244}; 245 246$.nette.ext('validation', { 247 before: function (xhr, settings) { 248 if (!settings.nette) return true; 249 else var analyze = settings.nette; 250 var e = analyze.e; 251 252 var validate = $.extend({ 253 keys: true, 254 url: true, 255 form: true 256 }, settings.validate || (function () { 257 if (!analyze.el.is('[data-ajax-validate]')) return; 258 var attr = analyze.el.data('ajaxValidate'); 259 if (attr === false) return { 260 keys: false, 261 url: false, 262 form: false 263 }; else if (typeof attr == 'object') return attr; 264 })() || {}); 265 266 var passEvent = false; 267 if (analyze.el.attr('data-ajax-pass') !== undefined) { 268 passEvent = analyze.el.data('ajaxPass'); 269 passEvent = typeof passEvent == 'bool' ? passEvent : true; 270 } 271 272 if (validate.keys) { 273 // thx to @vrana 274 var explicitNoAjax = e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey; 275 276 if (analyze.form) { 277 if (explicitNoAjax && analyze.isSubmit) { 278 this.explicitNoAjax = true; 279 return false; 280 } else if (analyze.isForm && this.explicitNoAjax) { 281 this.explicitNoAjax = false; 282 return false; 283 } 284 } else if (explicitNoAjax) return false; 285 } 286 287 if (validate.form && analyze.form && !((analyze.isSubmit || analyze.isImage) && analyze.el.attr('formnovalidate') !== undefined)) { 288 if (analyze.form.get(0).onsubmit && analyze.form.get(0).onsubmit() === false) { 289 e.stopImmediatePropagation(); 290 e.preventDefault(); 291 return false; 292 } 293 } 294 295 if (validate.url) { 296 // thx to @vrana 297 if (/:|^#/.test(analyze.form ? settings.url : analyze.el.attr('href'))) return false; 298 } 299 300 if (!passEvent) { 301 e.stopPropagation(); 302 e.preventDefault(); 303 } 304 return true; 305 } 306}, { 307 explicitNoAjax: false 308}); 309 310$.nette.ext('forms', { 311 init: function () { 312 var snippets; 313 if (!window.Nette || !(snippets = this.ext('snippets'))) return; 314 315 snippets.after(function ($el) { 316 $el.find('form').each(function() { 317 window.Nette.initForm(this); 318 }); 319 }); 320 }, 321 prepare: function (settings) { 322 var analyze = settings.nette; 323 if (!analyze || !analyze.form) return; 324 var e = analyze.e; 325 var originalData = settings.data || {}; 326 var formData = {}; 327 328 if (analyze.isSubmit) {
329 formData[analyze.el.attr('name')] = analyze.el.val() || ''; 330 } else if (analyze.isImage) { 331 var offset = analyze.el.offset(); 332 var name = analyze.el.attr('name'); 333 var dataOffset = [ Math.max(0, e.pageX - offset.left), Math.max(0, e.pageY - offset.top) ]; 334 335 if (name.indexOf('[', 0) !== -1) { // inside a container 336 formData[name] = dataOffset; 337 } else { 338 formData[name + '.x'] = dataOffset[0]; 339 formData[name + '.y'] = dataOffset[1]; 340 } 341 } 342 343 if (typeof originalData != 'string') { 344 originalData = $.param(originalData); 345 } 346 formData = $.param(formData); 347 settings.data = analyze.form.serialize() + (formData ? '&' + formData : '') + '&' + originalData; 348 } 349}); 350 351// default snippet handler 352$.nette.ext('snippets', { 353 success: function (payload) { 354 var snippets = []; 355 if (payload.snippets) { 356 for (var i in payload.snippets) { 357 var $el = this.getElement(i); 358 $.each(this.beforeQueue, function (index, callback) { 359 if (typeof callback == 'function') { 360 callback($el); 361 } 362 }); 363 this.updateSnippet($el, payload.snippets[i]); 364 $.each(this.afterQueue, function (index, callback) { 365 if (typeof callback == 'function') { 366 callback($el); 367 } 368 }); 369 } 370 } 371 this.before(snippets); 372 } 373}, { 374 beforeQueue: [], 375 afterQueue: [], 376 before: function (callback) { 377 this.beforeQueue.push(callback); 378 }, 379 after: function (callback) { 380 this.afterQueue.push(callback); 381 }, 382 updateSnippet: function ($el, html, back) { 383 if (typeof $el == 'string') { 384 $el = this.getElement($el); 385 } 386 // Fix for setting document title in IE
387 if ($el.is('title')) { 388 document.title = html; 389 } else { 390 this.applySnippet($el, html, back); 391 } 392 }, 393 getElement: function (id) { 394 return $('#' + this.escapeSelector(id)); 395 }, 396 applySnippet: function ($el, html, back) { 397 if (!back && $el.is('[data-ajax-append]')) { 398 $el.append(html); 399 } else { 400 $el.html(html); 401 } 402 }, 403 escapeSelector: function (selector) { 404 // thx to @uestla (https://github.com/uestla) 405 return selector.replace(/[\!"#\$%&'\(\)\*\+,\.\/:;<=>\?@\[\\\]\^`\{\|\}~]/g, '\\$&'); 406 } 407}); 408 409// support $this->redirect() 410$.nette.ext('redirect', { 411 success: function (payload) { 412 if (payload.redirect) { 413 window.location.href = payload.redirect; 414 return false; 415 } 416 } 417}); 418 419// current page state 420$.nette.ext('state', { 421 success: function (payload) { 422 if (payload.state) { 423 this.state = payload.state; 424 } 425 } 426}, {state: null}); 427 428// abort last request if new started 429$.nette.ext('unique', { 430 start: function (xhr) { 431 if (this.xhr) { 432 this.xhr.abort(); 433 } 434 this.xhr = xhr; 435 }, 436 complete: function () { 437 this.xhr = null; 438 } 439}, {xhr: null}); 440 441// option to abort by ESC (thx to @vrana) 442$.nette.ext('abort', { 443 init: function () { 444 $('body').keydown($.proxy(function (e) { 445 if (this.xhr && (e.keyCode == 27 // Esc 446 && !(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey)) 447 ) { 448 this.xhr.abort(); 449 } 450 }, this)); 451 }, 452 start: function (xhr) { 453 this.xhr = xhr; 454 }, 455 complete: function () { 456 this.xhr = null; 457 } 458}, {xhr: null}); 459 460$.nette.ext('load', { 461 success: function () { 462 $.nette.load(); 463 } 464}); 465 466// default ajaxification (can be overridden in init()) 467$.nette.ext('init', { 468 load: function (rh) { 469 $(this.linkSelector).off('click.nette', rh).on('click.nette', rh); 470 $(this.formSelector).off('submit.nette', rh).on('submit.nette', rh) 471 .off('click.nette', ':image', rh).on('click.nette', ':image', rh) 472 .off('click.nette', ':submit', rh).on('click.nette', ':submit', rh); 473 $(this.buttonSelector).closest('form') 474 .off('click.nette', this.buttonSelector, rh).on('click.nette', this.buttonSelector, rh); 475 } 476}, { 477 linkSelector: 'a.ajax', 478 formSelector: 'form.ajax', 479 buttonSelector: 'input.ajax[type="submit"], button.ajax[type="submit"], input.ajax[type="image"]' 480}); 481 482})(window, window.jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.