Yii2 framework backup
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

533 lines
20 KiB

/**
* Yii JavaScript module.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
/**
* yii is the root module for all Yii JavaScript modules.
* It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()".
*
* Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii").
*
* A module may be structured as follows:
*
* ```javascript
* window.yii.sample = (function($) {
* var pub = {
* // whether this module is currently active. If false, init() will not be called for this module
* // it will also not be called for all its child modules. If this property is undefined, it means true.
* isActive: true,
* init: function() {
* // ... module initialization code goes here ...
* },
*
* // ... other public functions and properties go here ...
* };
*
* // ... private functions and properties go here ...
*
* return pub;
* })(window.jQuery);
* ```
*
* Using this structure, you can define public and private functions/properties for a module.
* Private functions/properties are only visible within the module, while public functions/properties
* may be accessed outside of the module. For example, you can access "yii.sample.isActive".
*
* You must call "yii.initModule()" once for the root module of all your modules.
*/
window.yii = (function ($) {
var pub = {
/**
* List of JS or CSS URLs that can be loaded multiple times via AJAX requests.
* Each item may be represented as either an absolute URL or a relative one.
* Each item may contain a wildcard matching character `*`, that means one or more
* any characters on the position. For example:
* - `/css/*.css` will match any file ending with `.css` in the `css` directory of the current web site
* - `http*://cdn.example.com/*` will match any files on domain `cdn.example.com`, loaded with HTTP or HTTPS
* - `/js/myCustomScript.js?realm=*` will match file `/js/myCustomScript.js` with defined `realm` parameter
*/
reloadableScripts: [],
/**
* The selector for clickable elements that need to support confirmation and form submission.
*/
clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], ' +
'input[type="image"]',
/**
* The selector for changeable elements that need to support confirmation and form submission.
*/
changeableSelector: 'select, input, textarea',
/**
* @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled.
*/
getCsrfParam: function () {
return $('meta[name=csrf-param]').attr('content');
},
/**
* @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled.
*/
getCsrfToken: function () {
return $('meta[name=csrf-token]').attr('content');
},
/**
* Sets the CSRF token in the meta elements.
* This method is provided so that you can update the CSRF token with the latest one you obtain from the server.
* @param name the CSRF token name
* @param value the CSRF token value
*/
setCsrfToken: function (name, value) {
$('meta[name=csrf-param]').attr('content', name);
$('meta[name=csrf-token]').attr('content', value);
},
/**
* Updates all form CSRF input fields with the latest CSRF token.
* This method is provided to avoid cached forms containing outdated CSRF tokens.
*/
refreshCsrfToken: function () {
var token = pub.getCsrfToken();
if (token) {
$('form input[name="' + pub.getCsrfParam() + '"]').val(token);
}
},
/**
* Displays a confirmation dialog.
* The default implementation simply displays a js confirmation dialog.
* You may override this by setting `yii.confirm`.
* @param message the confirmation message.
* @param ok a callback to be called when the user confirms the message
* @param cancel a callback to be called when the user cancels the confirmation
*/
confirm: function (message, ok, cancel) {
if (window.confirm(message)) {
!ok || ok();
} else {
!cancel || cancel();
}
},
/**
* Handles the action triggered by user.
* This method recognizes the `data-method` attribute of the element. If the attribute exists,
* the method will submit the form containing this element. If there is no containing form, a form
* will be created and submitted using the method given by this attribute value (e.g. "post", "put").
* For hyperlinks, the form action will take the value of the "href" attribute of the link.
* For other elements, either the containing form action or the current page URL will be used
* as the form action URL.
*
* If the `data-method` attribute is not defined, the `href` attribute (if any) of the element
* will be assigned to `window.location`.
*
* Starting from version 2.0.3, the `data-params` attribute is also recognized when you specify
* `data-method`. The value of `data-params` should be a JSON representation of the data (name-value pairs)
* that should be submitted as hidden inputs. For example, you may use the following code to generate
* such a link:
*
* ```php
* use yii\helpers\Html;
* use yii\helpers\Json;
*
* echo Html::a('submit', ['site/foobar'], [
* 'data' => [
* 'method' => 'post',
* 'params' => [
* 'name1' => 'value1',
* 'name2' => 'value2',
* ],
* ],
* ]);
* ```
*
* @param $e the jQuery representation of the element
* @param event Related event
*/
handleAction: function ($e, event) {
var $form = $e.attr('data-form') ? $('#' + $e.attr('data-form')) : $e.closest('form'),
method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'),
action = $e.attr('href'),
isValidAction = action && action !== '#',
params = $e.data('params'),
areValidParams = params && $.isPlainObject(params),
pjax = $e.data('pjax'),
usePjax = pjax !== undefined && pjax !== 0 && $.support.pjax,
pjaxContainer,
pjaxOptions = {},
conflictParams = ['submit', 'reset', 'elements', 'length', 'name', 'acceptCharset',
'action', 'enctype', 'method', 'target'];
// Forms and their child elements should not use input names or ids that conflict with properties of a form,
// such as submit, length, or method.
$.each(conflictParams, function (index, param) {
if (areValidParams && params.hasOwnProperty(param)) {
console.error("Parameter name '" + param + "' conflicts with a same named form property. " +
"Please use another name.");
}
});
if (usePjax) {
pjaxContainer = $e.data('pjax-container');
if (pjaxContainer === undefined || !pjaxContainer.length) {
pjaxContainer = $e.closest('[data-pjax-container]').attr('id')
? ('#' + $e.closest('[data-pjax-container]').attr('id'))
: '';
}
if (!pjaxContainer.length) {
pjaxContainer = 'body';
}
pjaxOptions = {
container: pjaxContainer,
push: !!$e.data('pjax-push-state'),
replace: !!$e.data('pjax-replace-state'),
scrollTo: $e.data('pjax-scrollto'),
pushRedirect: $e.data('pjax-push-redirect'),
replaceRedirect: $e.data('pjax-replace-redirect'),
skipOuterContainers: $e.data('pjax-skip-outer-containers'),
timeout: $e.data('pjax-timeout'),
originalEvent: event,
originalTarget: $e
};
}
if (method === undefined) {
if (isValidAction) {
usePjax ? $.pjax.click(event, pjaxOptions) : window.location.assign(action);
} else if ($e.is(':submit') && $form.length) {
if (usePjax) {
$form.on('submit', function (e) {
$.pjax.submit(e, pjaxOptions);
});
}
$form.trigger('submit');
}
return;
}
var oldMethod,
oldAction,
newForm = !$form.length;
if (!newForm) {
oldMethod = $form.attr('method');
$form.attr('method', method);
if (isValidAction) {
oldAction = $form.attr('action');
$form.attr('action', action);
}
} else {
if (!isValidAction) {
action = pub.getCurrentUrl();
}
$form = $('<form/>', {method: method, action: action});
var target = $e.attr('target');
if (target) {
$form.attr('target', target);
}
if (!/(get|post)/i.test(method)) {
$form.append($('<input/>', {name: '_method', value: method, type: 'hidden'}));
method = 'post';
$form.attr('method', method);
}
if (/post/i.test(method)) {
var csrfParam = pub.getCsrfParam();
if (csrfParam) {
$form.append($('<input/>', {name: csrfParam, value: pub.getCsrfToken(), type: 'hidden'}));
}
}
$form.hide().appendTo('body');
}
var activeFormData = $form.data('yiiActiveForm');
if (activeFormData) {
// Remember the element triggered the form submission. This is used by yii.activeForm.js.
activeFormData.submitObject = $e;
}
if (areValidParams) {
$.each(params, function (name, value) {
$form.append($('<input/>').attr({name: name, value: value, type: 'hidden'}));
});
}
if (usePjax) {
$form.on('submit', function (e) {
$.pjax.submit(e, pjaxOptions);
});
}
$form.trigger('submit');
$.when($form.data('yiiSubmitFinalizePromise')).done(function () {
if (newForm) {
$form.remove();
return;
}
if (oldAction !== undefined) {
$form.attr('action', oldAction);
}
$form.attr('method', oldMethod);
if (areValidParams) {
$.each(params, function (name) {
$('input[name="' + name + '"]', $form).remove();
});
}
});
},
getQueryParams: function (url) {
var pos = url.indexOf('?');
if (pos < 0) {
return {};
}
var pairs = $.grep(url.substring(pos + 1).split('#')[0].split('&'), function (value) {
return value !== '';
});
var params = {};
for (var i = 0, len = pairs.length; i < len; i++) {
var pair = pairs[i].split('=');
var name = decodeURIComponent(pair[0].replace(/\+/g, '%20'));
var value = pair.length > 1 ? decodeURIComponent(pair[1].replace(/\+/g, '%20')) : '';
if (!name.length) {
continue;
}
if (params[name] === undefined) {
params[name] = value || '';
} else {
if (!$.isArray(params[name])) {
params[name] = [params[name]];
}
params[name].push(value || '');
}
}
return params;
},
initModule: function (module) {
if (module.isActive !== undefined && !module.isActive) {
return;
}
if ($.isFunction(module.init)) {
module.init();
}
$.each(module, function () {
if ($.isPlainObject(this)) {
pub.initModule(this);
}
});
},
init: function () {
initCsrfHandler();
initRedirectHandler();
initAssetFilters();
initDataMethods();
},
/**
* Returns the URL of the current page without params and trailing slash. Separated and made public for testing.
* @returns {string}
*/
getBaseCurrentUrl: function () {
return window.location.protocol + '//' + window.location.host;
},
/**
* Returns the URL of the current page. Used for testing, you can always call `window.location.href` manually
* instead.
* @returns {string}
*/
getCurrentUrl: function () {
return window.location.href;
}
};
function initCsrfHandler() {
// automatically send CSRF token for all AJAX requests
$.ajaxPrefilter(function (options, originalOptions, xhr) {
if (!options.crossDomain && pub.getCsrfParam()) {
xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
}
});
pub.refreshCsrfToken();
}
function initRedirectHandler() {
// handle AJAX redirection
$(document).ajaxComplete(function (event, xhr) {
var url = xhr && xhr.getResponseHeader('X-Redirect');
if (url) {
window.location.assign(url);
}
});
}
function initAssetFilters() {
/**
* Used for storing loaded scripts and information about loading each script if it's in the process of loading.
* A single script can have one of the following values:
*
* - `undefined` - script was not loaded at all before or was loaded with error last time.
* - `true` (boolean) - script was successfully loaded.
* - object - script is currently loading.
*
* In case of a value being an object the properties are:
* - `xhrList` - represents a queue of XHR requests sent to the same URL (related with this script) in the same
* small period of time.
* - `xhrDone` - boolean, acts like a locking mechanism. When one of the XHR requests in the queue is
* successfully completed, it will abort the rest of concurrent requests to the same URL until cleanup is done
* to prevent possible errors and race conditions.
* @type {{}}
*/
var loadedScripts = {};
$('script[src]').each(function () {
var url = getAbsoluteUrl(this.src);
loadedScripts[url] = true;
});
$.ajaxPrefilter('script', function (options, originalOptions, xhr) {
if (options.dataType == 'jsonp') {
return;
}
var url = getAbsoluteUrl(options.url),
forbiddenRepeatedLoad = loadedScripts[url] === true && !isReloadableAsset(url),
cleanupRunning = loadedScripts[url] !== undefined && loadedScripts[url]['xhrDone'] === true;
if (forbiddenRepeatedLoad || cleanupRunning) {
xhr.abort();
return;
}
if (loadedScripts[url] === undefined || loadedScripts[url] === true) {
loadedScripts[url] = {
xhrList: [],
xhrDone: false
};
}
xhr.done(function (data, textStatus, jqXHR) {
// If multiple requests were successfully loaded, perform cleanup only once
if (loadedScripts[jqXHR.yiiUrl]['xhrDone'] === true) {
return;
}
loadedScripts[jqXHR.yiiUrl]['xhrDone'] = true;
for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) {
var singleXhr = loadedScripts[jqXHR.yiiUrl]['xhrList'][i];
if (singleXhr && singleXhr.readyState !== XMLHttpRequest.DONE) {
singleXhr.abort();
}
}
loadedScripts[jqXHR.yiiUrl] = true;
}).fail(function (jqXHR, textStatus) {
if (textStatus === 'abort') {
return;
}
delete loadedScripts[jqXHR.yiiUrl]['xhrList'][jqXHR.yiiIndex];
var allFailed = true;
for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) {
if (loadedScripts[jqXHR.yiiUrl]['xhrList'][i]) {
allFailed = false;
}
}
if (allFailed) {
delete loadedScripts[jqXHR.yiiUrl];
}
});
// Use prefix for custom XHR properties to avoid possible conflicts with existing properties
xhr.yiiIndex = loadedScripts[url]['xhrList'].length;
xhr.yiiUrl = url;
loadedScripts[url]['xhrList'][xhr.yiiIndex] = xhr;
});
$(document).ajaxComplete(function () {
var styleSheets = [];
$('link[rel=stylesheet]').each(function () {
var url = getAbsoluteUrl(this.href);
if (isReloadableAsset(url)) {
return;
}
$.inArray(url, styleSheets) === -1 ? styleSheets.push(url) : $(this).remove();
});
});
}
function initDataMethods() {
var handler = function (event) {
var $this = $(this),
method = $this.data('method'),
message = $this.data('confirm'),
form = $this.data('form');
if (method === undefined && message === undefined && form === undefined) {
return true;
}
if (message !== undefined && message !== false && message !== '') {
$.proxy(pub.confirm, this)(message, function () {
pub.handleAction($this, event);
});
} else {
pub.handleAction($this, event);
}
event.stopImmediatePropagation();
return false;
};
// handle data-confirm and data-method for clickable and changeable elements
$(document).on('click.yii', pub.clickableSelector, handler)
.on('change.yii', pub.changeableSelector, handler);
}
function isReloadableAsset(url) {
for (var i = 0; i < pub.reloadableScripts.length; i++) {
var rule = getAbsoluteUrl(pub.reloadableScripts[i]);
var match = new RegExp("^" + escapeRegExp(rule).split('\\*').join('.+') + "$").test(url);
if (match === true) {
return true;
}
}
return false;
}
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
/**
* Returns absolute URL based on the given URL
* @param {string} url Initial URL
* @returns {string}
*/
function getAbsoluteUrl(url) {
return url.charAt(0) === '/' ? pub.getBaseCurrentUrl() + url : url;
}
return pub;
})(window.jQuery);
window.jQuery(function () {
window.yii.initModule(window.yii);
});