1/* ======================================================================== 2 * OctoberCMS: front-end JavaScript framework 3 * http://octobercms.com 4 * ======================================================================== 5 * Copyright 2017 Alexey Bobkov, Samuel Georges 6 * ======================================================================== */ 7 8if (window.jQuery === undefined) { 9 throw new Error('The jQuery library is not loaded. The OctoberCMS framework cannot be initialized.'); 10} 11if (window.jQuery.request !== undefined) { 12 throw new Error('The OctoberCMS framework is already loaded.'); 13} 14 15+function ($) { "use strict"; 16 17 var Request = function (element, handler, options) { 18 var $el = this.$el = $(element); 19 this.options = options || {}; 20 21 /* 22 * Validate handler name 23 */ 24 if (handler === undefined) { 25 throw new Error('The request handler name is not specified.') 26 } 27 28 if (!handler.match(/^(?:\w+\:{2})?on*/)) { 29 throw new Error('Invalid handler name. The correct handler name format is: "onEvent".') 30 } 31 32 /* 33 * Prepare the options and execute the request 34 */ 35 var $form = options.form ? $(options.form) : $el.closest('form'), 36 $triggerEl = !!$form.length ? $form : $el, 37 context = { handler: handler, options: options } 38 39 $el.trigger('ajaxSetup', [context]) 40 var _event = jQuery.Event('oc.beforeRequest') 41 $triggerEl.trigger(_event, context) 42 if (_event.isDefaultPrevented()) return 43 44 var data = [$form.serialize()], 45 loading = options.loading !== undefined ? options.loading : null, 46 isRedirect = options.redirect !== undefined && options.redirect.length, 47 useFlash = options.flash !== undefined 48 49 $.each($el.parents('[data-request-data]').toArray().reverse(), function extendRequest() { 50 data.push($.param(paramToObj('data-request-data', $(this).data('request-data')))) 51 }) 52 53 if ($el.is(':input') && !$form.length) { 54 var inputName = $el.attr('name') 55 if (inputName !== undefined && options.data[inputName] === undefined) 56 options.data[inputName] = $el.val() 57 } 58 59 if (options.data !== undefined && !$.isEmptyObject(options.data)) { 60 data.push($.param(options.data)) 61 } 62 63 if ($.type(loading) == 'string') { 64 loading = $(loading) 65 } 66 67 var requestHeaders = { 68 'X-OCTOBER-REQUEST-HANDLER': handler, 69 'X-OCTOBER-REQUEST-PARTIALS': this.extractPartials(options.update) 70 } 71 72 if (useFlash) { 73 requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1 74 } 75 76 var requestOptions = { 77 url: window.location.href, 78 crossDomain: false, 79 context: context, 80 headers: requestHeaders, 81 success: function(data, textStatus, jqXHR) { 82 /* 83 * Halt here if beforeUpdate() or data-request-before-update returns false 84 */ 85 if (this.options.beforeUpdate.apply(this, [data, textStatus, jqXHR]) === false) return 86 if (options.evalBeforeUpdate && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalBeforeUpdate+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') === false) return 87 88 /* 89 * Trigger 'ajaxBeforeUpdate' on the form, halt if event.preventDefault() is called 90 */ 91 var _event = jQuery.Event('ajaxBeforeUpdate') 92 $triggerEl.trigger(_event, [context, data, textStatus, jqXHR]) 93 if (_event.isDefaultPrevented()) return 94 95 if (useFlash && data['X_OCTOBER_FLASH_MESSAGES']) { 96 $.each(data['X_OCTOBER_FLASH_MESSAGES'], function(type, message) { 97 requestOptions.handleFlashMessage(message, type) 98 }) 99 } 100 101 /* 102 * Proceed with the update process 103 */ 104 var updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR) 105 106 updatePromise.done(function() {
107 $triggerEl.trigger('ajaxSuccess', [context, data, textStatus, jqXHR]) 108 options.evalSuccess && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalSuccess+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') 109 }) 110 111 return updatePromise 112 }, 113 error: function(jqXHR, textStatus, errorThrown) { 114 var errorMsg, 115 updatePromise = $.Deferred() 116 117 if ((window.ocUnloading !== undefined && window.ocUnloading) || errorThrown == 'abort') 118 return 119 120 /* 121 * Disable redirects 122 */ 123 isRedirect = false 124 options.redirect = null 125 126 /* 127 * Error 406 is a "smart error" that returns response object that is 128 * processed in the same fashion as a successful response. 129 */ 130 if (jqXHR.status == 406 && jqXHR.responseJSON) { 131 errorMsg = jqXHR.responseJSON['X_OCTOBER_ERROR_MESSAGE'] 132 updatePromise = requestOptions.handleUpdateResponse(jqXHR.responseJSON, textStatus, jqXHR) 133 } 134 /* 135 * Standard error with standard response text 136 */ 137 else { 138 errorMsg = jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText 139 updatePromise.resolve() 140 } 141 142 updatePromise.done(function() { 143 $el.data('error-message', errorMsg) 144 145 /* 146 * Trigger 'ajaxError' on the form, halt if event.preventDefault() is called 147 */ 148 var _event = jQuery.Event('ajaxError') 149 $triggerEl.trigger(_event, [context, errorMsg, textStatus, jqXHR]) 150 if (_event.isDefaultPrevented()) return 151 152 /* 153 * Halt here if the data-request-error attribute returns false 154 */ 155 if (options.evalError && eval('(function($el, context, errorMsg, textStatus, jqXHR) {'+options.evalError+'}.call($el.get(0), $el, context, errorMsg, textStatus, jqXHR))') === false) 156 return 157 158 requestOptions.handleErrorMessage(errorMsg) 159 }) 160 161 return updatePromise 162 }, 163 complete: function(data, textStatus, jqXHR) { 164 $triggerEl.trigger('ajaxComplete', [context, data, textStatus, jqXHR]) 165 options.evalComplete && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalComplete+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') 166 }, 167 168 /* 169 * Custom function, requests confirmation from the user 170 */ 171 handleConfirmMessage: function(message) { 172 var _event = jQuery.Event('ajaxConfirmMessage') 173 174 _event.promise = $.Deferred() 175 if ($(window).triggerHandler(_event, [message]) !== undefined) { 176 _event.promise.done(function() { 177 options.confirm = null 178 new Request(element, handler, options) 179 }) 180 return false 181 } 182 183 if (_event.isDefaultPrevented()) return 184 if (message) return confirm(message) 185 }, 186 187 /* 188 * Custom function, display an error message to the user 189 */ 190 handleErrorMessage: function(message) { 191 var _event = jQuery.Event('ajaxErrorMessage') 192 $(window).trigger(_event, [message]) 193 if (_event.isDefaultPrevented()) return 194 if (message) alert(message) 195 }, 196 197 /* 198 * Custom function, focus fields with errors 199 */ 200 handleValidationMessage: function(message, fields) {
201 $triggerEl.trigger('ajaxValidation', [context, message, fields]) 202 203 var isFirstInvalidField = true 204 $.each(fields, function focusErrorField(fieldName, fieldMessages) { 205 var fieldElement = $form.find('[name="'+fieldName+'"], [name="'+fieldName+'[]"], [name$="['+fieldName+']"], [name$="['+fieldName+'][]"]').filter(':enabled').first() 206 if (fieldElement.length > 0) { 207 208 var _event = jQuery.Event('ajaxInvalidField') 209 $(window).trigger(_event, [fieldElement.get(0), fieldName, fieldMessages, isFirstInvalidField]) 210 211 if (isFirstInvalidField) { 212 if (!_event.isDefaultPrevented()) fieldElement.focus() 213 isFirstInvalidField = false 214 } 215 } 216 }) 217 }, 218 219 /* 220 * Custom function, display a flash message to the user 221 */ 222 handleFlashMessage: function(message, type) {}, 223 224 /* 225 * Custom function, redirect the browser to another location 226 */ 227 handleRedirectResponse: function(url) { 228 window.location.href = url 229 }, 230 231 /* 232 * Custom function, handle any application specific response values 233 * Using a promisary object here in case injected assets need time to load 234 */ 235 handleUpdateResponse: function(data, textStatus, jqXHR) { 236 237 /* 238 * Update partials and finish request 239 */ 240 var updatePromise = $.Deferred().done(function() { 241 for (var partial in data) { 242 /* 243 * If a partial has been supplied on the client side that matches the server supplied key, look up 244 * it's selector and use that. If not, we assume it is an explicit selector reference. 245 */ 246 var selector = (options.update[partial]) ? options.update[partial] : partial 247 if ($.type(selector) == 'string' && selector.charAt(0) == '@') { 248 $(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 249 } 250 else if ($.type(selector) == 'string' && selector.charAt(0) == '^') { 251 $(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 252 } 253 else { 254 $(selector).trigger('ajaxBeforeReplace') 255 $(selector).html(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 256 } 257 } 258 259 /* 260 * Wait for .html() method to finish rendering from partial updates 261 */ 262 setTimeout(function() { 263 $(window) 264 .trigger('ajaxUpdateComplete', [context, data, textStatus, jqXHR]) 265 .trigger('resize') 266 }, 0) 267 }) 268 269 /* 270 * Handle redirect 271 */ 272 if (data['X_OCTOBER_REDIRECT']) { 273 options.redirect = data['X_OCTOBER_REDIRECT'] 274 isRedirect = true 275 } 276 277 if (isRedirect) { 278 requestOptions.handleRedirectResponse(options.redirect) 279 } 280 281 /* 282 * Handle validation 283 */ 284 if (data['X_OCTOBER_ERROR_FIELDS']) { 285 requestOptions.handleValidationMessage(data['X_OCTOBER_ERROR_MESSAGE'], data['X_OCTOBER_ERROR_FIELDS']) 286 } 287 288 /* 289 * Handle asset injection 290 */ 291 if (data['X_OCTOBER_ASSETS']) { 292 assetManager.load(data['X_OCTOBER_ASSETS'], $.proxy(updatePromise.resolve, updatePromise)) 293 } 294 else { 295 updatePromise.resolve() 296 } 297 298 return updatePromise 299 } 300 } 301 302 /* 303 * Allow default business logic to be called from user functions 304 */ 305 context.success = requestOptions.success 306 context.error = requestOptions.error 307 context.complete = requestOptions.complete 308 requestOptions = $.extend(requestOptions, options) 309 requestOptions.data = data.join('&') 310 311 /* 312 * Initiate request 313 */ 314 if (options.confirm && !requestOptions.handleConfirmMessage(options.confirm)) { 315 return 316 } 317 318 if (loading) loading.show()
319 $(window).trigger('ajaxBeforeSend', [context]) 320 $el.trigger('ajaxPromise', [context]) 321 322 return $.ajax(requestOptions) 323 .fail(function(jqXHR, textStatus, errorThrown) { 324 if (!isRedirect) { 325 $el.trigger('ajaxFail', [context, textStatus, jqXHR]) 326 if (loading) loading.hide() 327 } 328 }) 329 .done(function(data, textStatus, jqXHR) { 330 if (!isRedirect) { 331 $el.trigger('ajaxDone', [context, data, textStatus, jqXHR]) 332 if (loading) loading.hide() 333 } 334 }) 335 .always(function(dataOrXhr, textStatus, xhrOrError) { 336 $el.trigger('ajaxAlways', [context, dataOrXhr, textStatus, xhrOrError]) 337 }) 338 } 339 340 Request.DEFAULTS = { 341 update: {}, 342 type : 'POST', 343 beforeUpdate: function(data, textStatus, jqXHR) {}, 344 evalBeforeUpdate: null, 345 evalSuccess: null, 346 evalError: null, 347 evalComplete: null, 348 } 349 350 /* 351 * Internal function, build a string of partials and their update elements. 352 */ 353 Request.prototype.extractPartials = function(update) { 354 var result = [] 355 356 for (var partial in update) 357 result.push(partial) 358 359 return result.join('&') 360 } 361 362 // REQUEST PLUGIN DEFINITION 363 // ============================ 364 365 var old = $.fn.request 366 367 $.fn.request = function(handler, option) { 368 var args = arguments 369 370 var $this = $(this).first() 371 var data = { 372 evalBeforeUpdate: $this.data('request-before-update'), 373 evalSuccess: $this.data('request-success'), 374 evalError: $this.data('request-error'), 375 evalComplete: $this.data('request-complete'), 376 confirm: $this.data('request-confirm'), 377 redirect: $this.data('request-redirect'), 378 loading: $this.data('request-loading'), 379 flash: $this.data('request-flash'), 380 form: $this.data('request-form'), 381 update: paramToObj('data-request-update', $this.data('request-update')), 382 data: paramToObj('data-request-data', $this.data('request-data')) 383 } 384 if (!handler) handler = $this.data('request') 385 var options = $.extend(true, {}, Request.DEFAULTS, data, typeof option == 'object' && option) 386 return new Request($this, handler, options) 387 } 388 389 $.fn.request.Constructor = Request 390 391 $.request = function(handler, option) { 392 return $(document).request(handler, option) 393 } 394 395 // REQUEST NO CONFLICT 396 // ================= 397 398 $.fn.request.noConflict = function() { 399 $.fn.request = old 400 return this 401 } 402 403 // REQUEST DATA-API 404 // ============== 405 406 function paramToObj(name, value) { 407 if (value === undefined) value = '' 408 if (typeof value == 'object') return value 409 410 try { 411 return JSON.parse(JSON.stringify(eval("({" + value + "})"))) 412 } 413 catch (e) { 414 throw new Error('Error parsing the '+name+' attribute value. '+e) 415 } 416 } 417 418 $(document).on('change', 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request]', function documentOnChange() { 419 $(this).request() 420 }) 421 422 $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function documentOnClick(e) { 423 e.preventDefault() 424 425 $(this).request() 426 427 if ($(this).is('[type=submit]')) 428 return false 429 }) 430 431 $(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function documentOnKeydown(e) { 432 if (e.keyCode == 13) { 433 if (this.dataTrackInputTimer !== undefined) 434 window.clearTimeout(this.dataTrackInputTimer) 435 436 $(this).request() 437 return false 438 } 439 }) 440 441 $(document).on('keyup', 'input[data-request][data-track-input]', function documentOnKeyup(e) { 442 var 443 $el = $(this),
444 lastValue = $el.data('oc.lastvalue') 445 446 if (!$el.is('[type=email],[type=number],[type=password],[type=search],[type=text]')) 447 return 448 449 if (lastValue !== undefined && lastValue == this.value) 450 return 451 452 $el.data('oc.lastvalue', this.value) 453 454 if (this.dataTrackInputTimer !== undefined) 455 window.clearTimeout(this.dataTrackInputTimer) 456 457 var interval = $(this).data('track-input') 458 if (!interval) 459 interval = 300 460 461 var self = this 462 this.dataTrackInputTimer = window.setTimeout(function() { 463 $(self).request() 464 }, interval) 465 }) 466 467 $(document).on('submit', '[data-request]', function documentOnSubmit() { 468 $(this).request() 469 return false 470 }) 471 472 $(window).on('beforeunload', function documentOnBeforeUnload() { 473 window.ocUnloading = true 474 }) 475 476 /* 477 * Invent our own event that unifies document.ready with window.ajaxUpdateComplete 478 * 479 * $(document).render(function() { }) 480 * $(document).on('render', function() { }) 481 */ 482 483 $(document).ready(function triggerRenderOnReady() { 484 $(document).trigger('render') 485 }) 486 487 $(window).on('ajaxUpdateComplete', function triggerRenderOnAjaxUpdateComplete() { 488 $(document).trigger('render') 489 }) 490 491 $.fn.render = function(callback) { 492 $(document).on('render', callback) 493 } 494 495}(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.