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