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 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 // automatically send CSRF token for all AJAX requests 358 $.ajaxPrefilter(function (options, originalOptions, xhr) { 359 if (!options.crossDomain && pub.getCsrfParam()) { 360 xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); 361 } 362 }); 363 pub.refreshCsrfToken(); 364 } 365 366 function initRedirectHandler() { 367 // handle AJAX redirection 368 $(document).ajaxComplete(function (event, xhr) { 369 var url = xhr && xhr.getResponseHeader('X-Redirect'); 370 if (url) { 371 window.location.assign(url); 372 } 373 }); 374 } 375 376 function initAssetFilters() { 377 /** 378 * Used for storing loaded scripts and information about loading each script if it's in the process of loading. 379 * A single script can have one of the following values: 380 * 381 * - `undefined` - script was not loaded at all before or was loaded with error last time. 382 * - `true` (boolean) - script was successfully loaded. 383 * - object - script is currently loading. 384 * 385 * In case of a value being an object the properties are: 386 * - `xhrList` - represents a queue of XHR requests sent to the same URL (related with this script) in the same 387 * small period of time. 388 * - `xhrDone` - boolean, acts like a locking mechanism. When one of the XHR requests in the queue is 389 * successfully completed, it will abort the rest of concurrent requests to the same URL until cleanup is done 390 * to prevent possible errors and race conditions. 391 * @type {{}} 392 */ 393 var loadedScripts = {}; 394 395 $('script[src]').each(function () { 396 var url = getAbsoluteUrl(this.src); 397 loadedScripts[url] = true; 398 }); 399 400 $.ajaxPrefilter('script', function (options, originalOptions, xhr) { 401 if (options.dataType == 'jsonp') { 402 return; 403 } 404 405 var url = getAbsoluteUrl(options.url), 406 forbiddenRepeatedLoad = loadedScripts[url] === true && !isReloadableAsset(url), 407 cleanupRunning = loadedScripts[url] !== undefined && loadedScripts[url]['xhrDone'] === true; 408 409 if (forbiddenRepeatedLoad || cleanupRunning) { 410 xhr.abort(); 411 return; 412 } 413 414 if (loadedScripts[url] === undefined || loadedScripts[url] === true) { 415 loadedScripts[url] = { 416 xhrList: [], 417 xhrDone: false 418 }; 419 } 420 421 xhr.done(function (data, textStatus, jqXHR) { 422 // If multiple requests were successfully loaded, perform cleanup only once 423 if (loadedScripts[jqXHR.yiiUrl]['xhrDone'] === true) { 424 return; 425 } 426 427 loadedScripts[jqXHR.yiiUrl]['xhrDone'] = true; 428 429 for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) { 430 var singleXhr = loadedScripts[jqXHR.yiiUrl]['xhrList'][i]; 431 if (singleXhr && singleXhr.readyState !== XMLHttpRequest.DONE) { 432 singleXhr.abort(); 433 } 434 } 435 436 loadedScripts[jqXHR.yiiUrl] = true; 437 }).fail(function (jqXHR, textStatus) { 438 if (textStatus === 'abort') { 439 return; 440 } 441 442 delete loadedScripts[jqXHR.yiiUrl]['xhrList'][jqXHR.yiiIndex]; 443 444 var allFailed = true; 445 for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) { 446 if (loadedScripts[jqXHR.yiiUrl]['xhrList'][i]) { 447 allFailed = false;
448 } 449 } 450 451 if (allFailed) { 452 delete loadedScripts[jqXHR.yiiUrl]; 453 } 454 }); 455 // Use prefix for custom XHR properties to avoid possible conflicts with existing properties 456 xhr.yiiIndex = loadedScripts[url]['xhrList'].length; 457 xhr.yiiUrl = url; 458 459 loadedScripts[url]['xhrList'][xhr.yiiIndex] = xhr; 460 }); 461 462 $(document).ajaxComplete(function () { 463 var styleSheets = []; 464 $('link[rel=stylesheet]').each(function () { 465 var url = getAbsoluteUrl(this.href); 466 if (isReloadableAsset(url)) { 467 return; 468 } 469 470 $.inArray(url, styleSheets) === -1 ? styleSheets.push(url) : $(this).remove(); 471 }); 472 }); 473 } 474 475 function initDataMethods() { 476 var handler = function (event) { 477 var $this = $(this), 478 method = $this.data('method'), 479 message = $this.data('confirm'), 480 form = $this.data('form'); 481 482 if (method === undefined && message === undefined && form === undefined) { 483 return true; 484 } 485 486 if (message !== undefined && message !== false && message !== '') { 487 $.proxy(pub.confirm, this)(message, function () { 488 pub.handleAction($this, event); 489 }); 490 } else { 491 pub.handleAction($this, event); 492 } 493 event.stopImmediatePropagation(); 494 return false; 495 }; 496 497 // handle data-confirm and data-method for clickable and changeable elements 498 $(document).on('click.yii', pub.clickableSelector, handler) 499 .on('change.yii', pub.changeableSelector, handler); 500 } 501 502 function isReloadableAsset(url) { 503 for (var i = 0; i < pub.reloadableScripts.length; i++) { 504 var rule = getAbsoluteUrl(pub.reloadableScripts[i]); 505 var match = new RegExp("^" + escapeRegExp(rule).split('\\*').join('.+') + "$").test(url); 506 if (match === true) { 507 return true; 508 } 509 } 510 511 return false; 512 } 513 514 // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 515 function escapeRegExp(str) { 516 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 517 } 518 519 /** 520 * Returns absolute URL based on the given URL 521 * @param {string} url Initial URL 522 * @returns {string} 523 */ 524 function getAbsoluteUrl(url) { 525 return url.charAt(0) === '/' ? pub.getBaseCurrentUrl() + url : url; 526 } 527 528 return pub; 529})(window.jQuery); 530 531window.jQuery(function () { 532 window.yii.initModule(window.yii); 533});
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.