1/** 2 * Yii JavaScript module. 3 * 4 * @link http://www.yiiframework.com/ 5 * @copyright Copyright (c) 2008 Yii Software LLC 6 * @license http://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 165 if (usePjax) { 166 pjaxContainer = $e.data('pjax-container') || $e.closest('[data-pjax-container]'); 167 if (!pjaxContainer.length) { 168 pjaxContainer = $('body'); 169 } 170 pjaxOptions = { 171 container: pjaxContainer, 172 push: !!$e.data('pjax-push-state'), 173 replace: !!$e.data('pjax-replace-state'), 174 scrollTo: $e.data('pjax-scrollto'), 175 pushRedirect: $e.data('pjax-push-redirect'), 176 replaceRedirect: $e.data('pjax-replace-redirect'), 177 skipOuterContainers: $e.data('pjax-skip-outer-containers'), 178 timeout: $e.data('pjax-timeout'), 179 originalEvent: event, 180 originalTarget: $e 181 }; 182 } 183 184 if (method === undefined) { 185 if (isValidAction) { 186 usePjax ? $.pjax.click(event, pjaxOptions) : window.location.assign(action); 187 } else if ($e.is(':submit') && $form.length) { 188 if (usePjax) { 189 $form.on('submit', function (e) { 190 $.pjax.submit(e, pjaxOptions); 191 }); 192 } 193 $form.trigger('submit'); 194 } 195 return; 196 } 197 198 var oldMethod, 199 oldAction, 200 newForm = !$form.length; 201 if (!newForm) { 202 oldMethod = $form.attr('method'); 203 $form.attr('method', method); 204 if (isValidAction) { 205 oldAction = $form.attr('action'); 206 $form.attr('action', action); 207 } 208 } else { 209 if (!isValidAction) { 210 action = pub.getCurrentUrl(); 211 } 212 $form = $('<form/>', {method: method, action: action}); 213 var target = $e.attr('target'); 214 if (target) { 215 $form.attr('target', target); 216 } 217 if (!/(get|post)/i.test(method)) { 218 $form.append($('<input/>', {name: '_method', value: method, type: 'hidden'})); 219 method = 'post'; 220 $form.attr('method', method); 221 } 222 if (/post/i.test(method)) { 223 var csrfParam = pub.getCsrfParam(); 224 if (csrfParam) { 225 $form.append($('<input/>', {name: csrfParam, value: pub.getCsrfToken(), type: 'hidden'})); 226 } 227 } 228 $form.hide().appendTo('body'); 229 } 230 231 var activeFormData = $form.data('yiiActiveForm'); 232 if (activeFormData) { 233 // Remember the element triggered the form submission. This is used by yii.activeForm.js. 234 activeFormData.submitObject = $e; 235 } 236 237 if (areValidParams) { 238 $.each(params, function (name, value) { 239 $form.append($('<input/>').attr({name: name, value: value, type: 'hidden'})); 240 }); 241 } 242 243 if (usePjax) { 244 $form.on('submit', function (e) { 245 $.pjax.submit(e, pjaxOptions); 246 }); 247 } 248 249 $form.trigger('submit'); 250 251 $.when($form.data('yiiSubmitFinalizePromise')).then(function () { 252 if (newForm) { 253 $form.remove(); 254 return; 255 } 256 257 if (oldAction !== undefined) { 258 $form.attr('action', oldAction); 259 } 260 $form.attr('method', oldMethod); 261 262 if (areValidParams) { 263 $.each(params, function (name) { 264 $('input[name="' + name + '"]', $form).remove(); 265 }); 266 } 267 }); 268 }, 269 270 getQueryParams: function (url) { 271 var pos = url.indexOf('?'); 272 if (pos < 0) { 273 return {}; 274 } 275 276 var pairs = $.grep(url.substring(pos + 1).split('#')[0].split('&'), function (value) { 277 return value !== ''; 278 }); 279 var params = {}; 280 281 for (var i = 0, len = pairs.length; i < len; i++) { 282 var pair = pairs[i].split('='); 283 var name = decodeURIComponent(pair[0].replace(/\+/g, '%20')); 284 var value = decodeURIComponent(pair[1].replace(/\+/g, '%20')); 285 if (!name.length) { 286 continue; 287 } 288 if (params[name] === undefined) { 289 params[name] = value || ''; 290 } else { 291 if (!$.isArray(params[name])) { 292 params[name] = [params[name]]; 293 } 294 params[name].push(value || ''); 295 } 296 } 297 return params; 298 }, 299 300 initModule: function (module) { 301 if (module.isActive !== undefined && !module.isActive) { 302 return; 303 } 304 if ($.isFunction(module.init)) {
305 module.init(); 306 } 307 $.each(module, function () { 308 if ($.isPlainObject(this)) { 309 pub.initModule(this); 310 } 311 }); 312 }, 313 314 init: function () { 315 initCsrfHandler(); 316 initRedirectHandler(); 317 initAssetFilters(); 318 initDataMethods(); 319 }, 320 321 /** 322 * Returns the URL of the current page without params and trailing slash. Separated and made public for testing. 323 * @returns {string} 324 */ 325 getBaseCurrentUrl: function () { 326 return window.location.protocol + '//' + window.location.host; 327 }, 328 329 /** 330 * Returns the URL of the current page. Used for testing, you can always call `window.location.href` manually 331 * instead. 332 * @returns {string} 333 */ 334 getCurrentUrl: function () { 335 return window.location.href; 336 } 337 }; 338 339 function initCsrfHandler() { 340 // automatically send CSRF token for all AJAX requests 341 $.ajaxPrefilter(function (options, originalOptions, xhr) { 342 if (!options.crossDomain && pub.getCsrfParam()) { 343 xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); 344 } 345 }); 346 pub.refreshCsrfToken(); 347 } 348 349 function initRedirectHandler() { 350 // handle AJAX redirection 351 $(document).ajaxComplete(function (event, xhr) { 352 var url = xhr && xhr.getResponseHeader('X-Redirect'); 353 if (url) { 354 window.location.assign(url); 355 } 356 }); 357 } 358 359 function initAssetFilters() { 360 /** 361 * Used for storing loaded scripts and information about loading each script if it's in the process of loading. 362 * A single script can have one of the following values: 363 * 364 * - `undefined` - script was not loaded at all before or was loaded with error last time. 365 * - `true` (boolean) - script was successfully loaded. 366 * - object - script is currently loading. 367 * 368 * In case of a value being an object the properties are: 369 * - `xhrList` - represents a queue of XHR requests sent to the same URL (related with this script) in the same 370 * small period of time. 371 * - `xhrDone` - boolean, acts like a locking mechanism. When one of the XHR requests in the queue is 372 * successfully completed, it will abort the rest of concurrent requests to the same URL until cleanup is done 373 * to prevent possible errors and race conditions. 374 * @type {{}} 375 */ 376 var loadedScripts = {}; 377 378 $('script[src]').each(function () { 379 var url = getAbsoluteUrl(this.src); 380 loadedScripts[url] = true; 381 }); 382 383 $.ajaxPrefilter('script', function (options, originalOptions, xhr) { 384 if (options.dataType == 'jsonp') { 385 return; 386 } 387 388 var url = getAbsoluteUrl(options.url), 389 forbiddenRepeatedLoad = loadedScripts[url] === true && !isReloadableAsset(url), 390 cleanupRunning = loadedScripts[url] !== undefined && loadedScripts[url]['xhrDone'] === true; 391 392 if (forbiddenRepeatedLoad || cleanupRunning) { 393 xhr.abort(); 394 return; 395 } 396 397 if (loadedScripts[url] === undefined || loadedScripts[url] === true) { 398 loadedScripts[url] = { 399 xhrList: [], 400 xhrDone: false 401 }; 402 } 403 404 xhr.done(function (data, textStatus, jqXHR) { 405 // If multiple requests were successfully loaded, perform cleanup only once 406 if (loadedScripts[jqXHR.yiiUrl]['xhrDone'] === true) { 407 return; 408 } 409 410 loadedScripts[jqXHR.yiiUrl]['xhrDone'] = true; 411 412 for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) { 413 var singleXhr = loadedScripts[jqXHR.yiiUrl]['xhrList'][i]; 414 if (singleXhr && singleXhr.readyState !== XMLHttpRequest.DONE) { 415 singleXhr.abort(); 416 } 417 } 418 419 loadedScripts[jqXHR.yiiUrl] = true; 420 }).fail(function (jqXHR, textStatus) { 421 if (textStatus === 'abort') { 422 return; 423 } 424 425 delete loadedScripts[jqXHR.yiiUrl]['xhrList'][jqXHR.yiiIndex]; 426 427 var allFailed = true; 428 for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) { 429 if (loadedScripts[jqXHR.yiiUrl]['xhrList'][i]) { 430 allFailed = false;
431 } 432 } 433 434 if (allFailed) { 435 delete loadedScripts[jqXHR.yiiUrl]; 436 } 437 }); 438 // Use prefix for custom XHR properties to avoid possible conflicts with existing properties 439 xhr.yiiIndex = loadedScripts[url]['xhrList'].length; 440 xhr.yiiUrl = url; 441 442 loadedScripts[url]['xhrList'][xhr.yiiIndex] = xhr; 443 }); 444 445 $(document).ajaxComplete(function () { 446 var styleSheets = []; 447 $('link[rel=stylesheet]').each(function () { 448 var url = getAbsoluteUrl(this.href); 449 if (isReloadableAsset(url)) { 450 return; 451 } 452 453 $.inArray(url, styleSheets) === -1 ? styleSheets.push(url) : $(this).remove(); 454 }); 455 }); 456 } 457 458 function initDataMethods() { 459 var handler = function (event) { 460 var $this = $(this), 461 method = $this.data('method'), 462 message = $this.data('confirm'), 463 form = $this.data('form'); 464 465 if (method === undefined && message === undefined && form === undefined) { 466 return true; 467 } 468 469 if (message !== undefined) { 470 $.proxy(pub.confirm, this)(message, function () { 471 pub.handleAction($this, event); 472 }); 473 } else { 474 pub.handleAction($this, event); 475 } 476 event.stopImmediatePropagation(); 477 return false; 478 }; 479 480 // handle data-confirm and data-method for clickable and changeable elements 481 $(document).on('click.yii', pub.clickableSelector, handler) 482 .on('change.yii', pub.changeableSelector, handler); 483 } 484 485 function isReloadableAsset(url) { 486 for (var i = 0; i < pub.reloadableScripts.length; i++) { 487 var rule = getAbsoluteUrl(pub.reloadableScripts[i]); 488 var match = new RegExp("^" + escapeRegExp(rule).split('\\*').join('.+') + "$").test(url); 489 if (match === true) { 490 return true; 491 } 492 } 493 494 return false; 495 } 496 497 // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 498 function escapeRegExp(str) { 499 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 500 } 501 502 /** 503 * Returns absolute URL based on the given URL 504 * @param {string} url Initial URL 505 * @returns {string} 506 */ 507 function getAbsoluteUrl(url) { 508 return url.charAt(0) === '/' ? pub.getBaseCurrentUrl() + url : url; 509 } 510 511 return pub; 512})(window.jQuery); 513 514window.jQuery(function () { 515 window.yii.initModule(window.yii); 516});
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.