1/** 2 * Yii JavaScript module. 3 * 4 * @link https://www.yiiframework.com/ 5 * @copyright Copyright (c) 2008 Yii Software LLC 6 * @license https://www.yiiframework.com/license/ 7 * @author Qiang Xue <[email protected]> 8 * @since 2.0 9 */ 10 11/** 12 * yii is the root module for all Yii JavaScript modules. 13 * It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()". 14 * 15 * Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii"). 16 * 17 * A module may be structured as follows: 18 * 19 * ```javascript 20 * window.yii.sample = (function($) { 21 * var pub = { 22 * // whether this module is currently active. If false, init() will not be called for this module 23 * // it will also not be called for all its child modules. If this property is undefined, it means true. 24 * isActive: true, 25 * init: function() { 26 * // ... module initialization code goes here ... 27 * }, 28 * 29 * // ... other public functions and properties go here ... 30 * }; 31 * 32 * // ... private functions and properties go here ... 33 * 34 * return pub; 35 * })(window.jQuery); 36 * ``` 37 * 38 * Using this structure, you can define public and private functions/properties for a module. 39 * Private functions/properties are only visible within the module, while public functions/properties 40 * may be accessed outside of the module. For example, you can access "yii.sample.isActive". 41 * 42 * You must call "yii.initModule()" once for the root module of all your modules. 43 */ 44window.yii = (function ($) { 45 var pub = { 46 /** 47 * List of JS or CSS URLs that can be loaded multiple times via AJAX requests. 48 * Each item may be represented as either an absolute URL or a relative one. 49 * Each item may contain a wildcard matching character `*`, that means one or more 50 * any characters on the position. For example: 51 * - `/css/*.css` will match any file ending with `.css` in the `css` directory of the current web site 52 * - `http*://cdn.example.com/*` will match any files on domain `cdn.example.com`, loaded with HTTP or HTTPS 53 * - `/js/myCustomScript.js?realm=*` will match file `/js/myCustomScript.js` with defined `realm` parameter 54 */ 55 reloadableScripts: [], 56 /** 57 * The selector for clickable elements that need to support confirmation and form submission. 58 */ 59 clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], ' + 60 'input[type="image"]', 61 /** 62 * The selector for changeable elements that need to support confirmation and form submission. 63 */ 64 changeableSelector: 'select, input, textarea', 65 66 /** 67 * @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled. 68 */
69 getCsrfParam: function () { 70 return $('meta[name=csrf-param]').attr('content'); 71 }, 72 73 /** 74 * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled. 75 */ 76 getCsrfToken: function () { 77 return $('meta[name=csrf-token]').attr('content'); 78 }, 79 80 /** 81 * Sets the CSRF token in the meta elements. 82 * This method is provided so that you can update the CSRF token with the latest one you obtain from the server. 83 * @param name the CSRF token name 84 * @param value the CSRF token value 85 */ 86 setCsrfToken: function (name, value) { 87 $('meta[name=csrf-param]').attr('content', name); 88 $('meta[name=csrf-token]').attr('content', value); 89 }, 90 91 /** 92 * Updates all form CSRF input fields with the latest CSRF token. 93 * This method is provided to avoid cached forms containing outdated CSRF tokens. 94 */ 95 refreshCsrfToken: function () { 96 var token = pub.getCsrfToken(); 97 if (token) { 98 $('form input[name="' + pub.getCsrfParam() + '"]').val(token); 99 } 100 }, 101 102 /** 103 * Displays a confirmation dialog. 104 * The default implementation simply displays a js confirmation dialog. 105 * You may override this by setting `yii.confirm`. 106 * @param message the confirmation message. 107 * @param ok a callback to be called when the user confirms the message 108 * @param cancel a callback to be called when the user cancels the confirmation 109 */ 110 confirm: function (message, ok, cancel) { 111 if (window.confirm(message)) { 112 !ok || ok(); 113 } else { 114 !cancel || cancel(); 115 } 116 }, 117 118 /** 119 * Handles the action triggered by user. 120 * This method recognizes the `data-method` attribute of the element. If the attribute exists, 121 * the method will submit the form containing this element. If there is no containing form, a form 122 * will be created and submitted using the method given by this attribute value (e.g. "post", "put"). 123 * For hyperlinks, the form action will take the value of the "href" attribute of the link. 124 * For other elements, either the containing form action or the current page URL will be used 125 * as the form action URL. 126 * 127 * If the `data-method` attribute is not defined, the `href` attribute (if any) of the element 128 * will be assigned to `window.location`. 129 * 130 * Starting from version 2.0.3, the `data-params` attribute is also recognized when you specify 131 * `data-method`. The value of `data-params` should be a JSON representation of the data (name-value pairs) 132 * that should be submitted as hidden inputs. For example, you may use the following code to generate 133 * such a link: 134 * 135 * ```php 136 * use yii\helpers\Html; 137 * use yii\helpers\Json; 138 * 139 * echo Html::a('submit', ['site/foobar'], [ 140 * 'data' => [ 141 * 'method' => 'post', 142 * 'params' => [ 143 * 'name1' => 'value1', 144 * 'name2' => 'value2', 145 * ], 146 * ], 147 * ]); 148 * ``` 149 * 150 * @param $e the jQuery representation of the element 151 * @param event Related event 152 */ 153 handleAction: function ($e, event) { 154 var $form = $e.attr('data-form') ? $('#' + $e.attr('data-form')) : $e.closest('form'), 155 method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'), 156 action = $e.attr('href'), 157 isValidAction = action && action !== '#', 158 params = $e.data('params'), 159 areValidParams = params && $.isPlainObject(params), 160 pjax = $e.data('pjax'), 161 usePjax = pjax !== undefined && pjax !== 0 && $.support.pjax, 162 pjaxContainer, 163 pjaxOptions = {}, 164 conflictParams = ['submit', 'reset', 'elements', 'length', 'name', 'acceptCharset', 165 'action', 'enctype', 'method', 'target']; 166 167 // Forms and their child elements should not use input names or ids that conflict with properties of a form, 168 // such as submit, length, or method. 169 $.each(conflictParams, function (index, param) { 170 if (areValidParams && params.hasOwnProperty(param)) { 171 console.error("Parameter name '" + param + "' conflicts with a same named form property. " + 172 "Please use another name."); 173 } 174 }); 175 176 if (usePjax) { 177 pjaxContainer = $e.data('pjax-container'); 178 if (pjaxContainer === undefined || !pjaxContainer.length) { 179 pjaxContainer = $e.closest('[data-pjax-container]').attr('id') 180 ? ('#' + $e.closest('[data-pjax-container]').attr('id')) 181 : ''; 182 } 183 if (!pjaxContainer.length) { 184 pjaxContainer = 'body'; 185 } 186 pjaxOptions = { 187 container: pjaxContainer, 188 push: !!$e.data('pjax-push-state'), 189 replace: !!$e.data('pjax-replace-state'), 190 scrollTo: $e.data('pjax-scrollto'), 191 pushRedirect: $e.data('pjax-push-redirect'), 192 replaceRedirect: $e.data('pjax-replace-redirect'), 193 skipOuterContainers: $e.data('pjax-skip-outer-containers'), 194 timeout: $e.data('pjax-timeout'), 195 originalEvent: event, 196 originalTarget: $e 197 }; 198 } 199 200 if (method === undefined) { 201 if (isValidAction) { 202 usePjax ? $.pjax.click(event, pjaxOptions) : window.location.assign(action); 203 } else if ($e.is(':submit') && $form.length) { 204 if (usePjax) { 205 $form.on('submit', function (e) { 206 $.pjax.submit(e, pjaxOptions); 207 }); 208 } 209 $form.trigger('submit'); 210 } 211 return; 212 } 213 214 var oldMethod, 215 oldAction, 216 newForm = !$form.length; 217 if (!newForm) { 218 oldMethod = $form.attr('method'); 219 $form.attr('method', method); 220 if (isValidAction) { 221 oldAction = $form.attr('action'); 222 $form.attr('action', action); 223 } 224 } else { 225 if (!isValidAction) { 226 action = pub.getCurrentUrl(); 227 } 228 $form = $('<form/>', {method: method, action: action}); 229 var target = $e.attr('target'); 230 if (target) { 231 $form.attr('target', target); 232 } 233 if (!/(get|post)/i.test(method)) { 234 $form.append($('<input/>', {name: '_method', value: method, type: 'hidden'})); 235 method = 'post'; 236 $form.attr('method', method); 237 } 238 if (/post/i.test(method)) { 239 var csrfParam = pub.getCsrfParam(); 240 if (csrfParam) { 241 $form.append($('<input/>', {name: csrfParam, value: pub.getCsrfToken(), type: 'hidden'})); 242 } 243 } 244 $form.hide().appendTo('body'); 245 } 246 247 var activeFormData = $form.data('yiiActiveForm'); 248 if (activeFormData) { 249 // Remember the element triggered the form submission. This is used by yii.activeForm.js. 250 activeFormData.submitObject = $e; 251 } 252 253 if (areValidParams) { 254 $.each(params, function (name, value) { 255 $form.append($('<input/>').attr({name: name, value: value, type: 'hidden'})); 256 }); 257 } 258 259 if (usePjax) { 260 $form.on('submit', function (e) { 261 $.pjax.submit(e, pjaxOptions); 262 }); 263 } 264 265 $form.trigger('submit'); 266 267 $.when($form.data('yiiSubmitFinalizePromise')).done(function () { 268 if (newForm) { 269 $form.remove(); 270 return; 271 } 272 273 if (oldAction !== undefined) { 274 $form.attr('action', oldAction); 275 } 276 $form.attr('method', oldMethod); 277 278 if (areValidParams) { 279 $.each(params, function (name) { 280 $('input[name="' + name + '"]', $form).remove(); 281 }); 282 } 283 }); 284 }, 285 286 getQueryParams: function (url) { 287 var pos = url.indexOf('?'); 288 if (pos < 0) { 289 return {}; 290 } 291 292 var pairs = $.grep(url.substring(pos + 1).split('#')[0].split('&'), function (value) { 293 return value !== ''; 294 }); 295 var params = {}; 296 297 for (var i = 0, len = pairs.length; i < len; i++) { 298 var pair = pairs[i].split('='); 299 var name = decodeURIComponent(pair[0].replace(/\+/g, '%20')); 300 var value = pair.length > 1 ? decodeURIComponent(pair[1].replace(/\+/g, '%20')) : ''; 301 if (!name.length) { 302 continue; 303 } 304 if (params[name] === undefined) { 305 params[name] = value || ''; 306 } else { 307 if (!$.isArray(params[name])) { 308 params[name] = [params[name]]; 309 } 310 params[name].push(value || ''); 311 } 312 } 313 314 return params; 315 }, 316 317 initModule: function (module) { 318 if (module.isActive !== undefined && !module.isActive) { 319 return; 320 } 321 if ($.isFunction(module.init)) {
322 module.init(); 323 } 324 $.each(module, function () { 325 if ($.isPlainObject(this)) { 326 pub.initModule(this); 327 } 328 }); 329 }, 330 331 init: function () { 332 initCsrfHandler(); 333 initRedirectHandler(); 334 initAssetFilters(); 335 initDataMethods(); 336 }, 337 338 /** 339 * Returns the URL of the current page without params and trailing slash. Separated and made public for testing. 340 * @returns {string} 341 */ 342 getBaseCurrentUrl: function () { 343 return window.location.protocol + '//' + window.location.host; 344 }, 345 346 /** 347 * Returns the URL of the current page. Used for testing, you can always call `window.location.href` manually 348 * instead. 349 * @returns {string} 350 */ 351 getCurrentUrl: function () { 352 return window.location.href; 353 } 354 }; 355 356 function initCsrfHandler() 357 { 358 // automatically send CSRF token for all AJAX requests 359 $.ajaxPrefilter(function (options, originalOptions, xhr) { 360 if (!options.crossDomain && pub.getCsrfParam()) { 361 xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); 362 } 363 }); 364 pub.refreshCsrfToken(); 365 } 366 367 function initRedirectHandler() 368 { 369 // handle AJAX redirection 370 $(document).ajaxComplete(function (event, xhr) { 371 var url = xhr && xhr.getResponseHeader('X-Redirect'); 372 if (url) { 373 window.location.assign(url); 374 } 375 }); 376 } 377 378 function initAssetFilters() 379 { 380 /** 381 * Used for storing loaded scripts and information about loading each script if it's in the process of loading. 382 * A single script can have one of the following values: 383 * 384 * - `undefined` - script was not loaded at all before or was loaded with error last time. 385 * - `true` (boolean) - script was successfully loaded. 386 * - object - script is currently loading. 387 * 388 * In case of a value being an object the properties are: 389 * - `xhrList` - represents a queue of XHR requests sent to the same URL (related with this script) in the same 390 * small period of time. 391 * - `xhrDone` - boolean, acts like a locking mechanism. When one of the XHR requests in the queue is 392 * successfully completed, it will abort the rest of concurrent requests to the same URL until cleanup is done 393 * to prevent possible errors and race conditions. 394 * @type {{}} 395 */ 396 var loadedScripts = {}; 397 398 $('script[src]').each(function () { 399 var url = getAbsoluteUrl(this.src); 400 loadedScripts[url] = true; 401 }); 402 403 $.ajaxPrefilter('script', function (options, originalOptions, xhr) { 404 if (options.dataType == 'jsonp') { 405 return; 406 } 407 408 var url = getAbsoluteUrl(options.url), 409 forbiddenRepeatedLoad = loadedScripts[url] === true && !isReloadableAsset(url), 410 cleanupRunning = loadedScripts[url] !== undefined && loadedScripts[url]['xhrDone'] === true; 411 412 if (forbiddenRepeatedLoad || cleanupRunning) { 413 xhr.abort(); 414 return; 415 } 416 417 if (loadedScripts[url] === undefined || loadedScripts[url] === true) { 418 loadedScripts[url] = { 419 xhrList: [], 420 xhrDone: false 421 }; 422 } 423 424 xhr.done(function (data, textStatus, jqXHR) { 425 // If multiple requests were successfully loaded, perform cleanup only once 426 if (loadedScripts[jqXHR.yiiUrl]['xhrDone'] === true) { 427 return; 428 } 429 430 loadedScripts[jqXHR.yiiUrl]['xhrDone'] = true; 431 432 for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) { 433 var singleXhr = loadedScripts[jqXHR.yiiUrl]['xhrList'][i]; 434 if (singleXhr && singleXhr.readyState !== XMLHttpRequest.DONE) { 435 singleXhr.abort(); 436 } 437 } 438 439 loadedScripts[jqXHR.yiiUrl] = true; 440 }).fail(function (jqXHR, textStatus) { 441 if (textStatus === 'abort') { 442 return; 443 } 444 445 delete loadedScripts[jqXHR.yiiUrl]['xhrList'][jqXHR.yiiIndex]; 446 447 var allFailed = true; 448 for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) { 449 if (loadedScripts[jqXHR.yiiUrl]['xhrList'][i]) { 450 allFailed = false;
451 } 452 } 453 454 if (allFailed) { 455 delete loadedScripts[jqXHR.yiiUrl]; 456 } 457 }); 458 // Use prefix for custom XHR properties to avoid possible conflicts with existing properties 459 xhr.yiiIndex = loadedScripts[url]['xhrList'].length; 460 xhr.yiiUrl = url; 461 462 loadedScripts[url]['xhrList'][xhr.yiiIndex] = xhr; 463 }); 464 465 $(document).ajaxComplete(function () { 466 var styleSheets = []; 467 $('link[rel=stylesheet]').each(function () { 468 var url = getAbsoluteUrl(this.href); 469 if (isReloadableAsset(url)) { 470 return; 471 } 472 473 $.inArray(url, styleSheets) === -1 ? styleSheets.push(url) : $(this).remove(); 474 }); 475 }); 476 } 477 478 function initDataMethods() 479 { 480 var handler = function (event) { 481 var $this = $(this), 482 method = $this.data('method'), 483 message = $this.data('confirm'), 484 form = $this.data('form'); 485 486 if (method === undefined && message === undefined && form === undefined) { 487 return true; 488 } 489 490 if (message !== undefined && message !== false && message !== '') { 491 $.proxy(pub.confirm, this)(message, function () { 492 pub.handleAction($this, event); 493 }); 494 } else { 495 pub.handleAction($this, event); 496 } 497 event.stopImmediatePropagation(); 498 return false; 499 }; 500 501 // handle data-confirm and data-method for clickable and changeable elements 502 $(document).on('click.yii', pub.clickableSelector, handler) 503 .on('change.yii', pub.changeableSelector, handler); 504 } 505 506 function isReloadableAsset(url) 507 { 508 for (var i = 0; i < pub.reloadableScripts.length; i++) { 509 var rule = getAbsoluteUrl(pub.reloadableScripts[i]); 510 var match = new RegExp("^" + escapeRegExp(rule).split('\\*').join('.+') + "$").test(url); 511 if (match === true) { 512 return true; 513 } 514 } 515 516 return false; 517 } 518 519 // https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex/6969486#6969486 520 function escapeRegExp(str) 521 { 522 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 523 } 524 525 /** 526 * Returns absolute URL based on the given URL 527 * @param {string} url Initial URL 528 * @returns {string} 529 */ 530 function getAbsoluteUrl(url) 531 { 532 return url.charAt(0) === '/' ? pub.getBaseCurrentUrl() + url : url; 533 } 534 535 return pub; 536})(window.jQuery); 537 538window.jQuery(function () { 539 window.yii.initModule(window.yii); 540});
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.