|
- /**
- * 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
- * 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 go here ...
- * },
- *
- * // ... other public functions and properties go here ...
- * };
- *
- * // ... private functions and properties go here ...
- *
- * return pub;
- * })(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.
- */
- yii = (function ($) {
- var pub = {
- /**
- * List of JS or CSS URLs that can be loaded multiple times via AJAX requests. Each script can be represented
- * as either an absolute URL or a relative one.
- */
- 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 (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
- */
- 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'),
- params = $e.data('params'),
- pjax = $e.data('pjax'),
- pjaxPushState = !!$e.data('pjax-push-state'),
- pjaxReplaceState = !!$e.data('pjax-replace-state'),
- pjaxTimeout = $e.data('pjax-timeout'),
- pjaxScrollTo = $e.data('pjax-scrollto'),
- pjaxPushRedirect = $e.data('pjax-push-redirect'),
- pjaxReplaceRedirect = $e.data('pjax-replace-redirect'),
- pjaxSkipOuterContainers = $e.data('pjax-skip-outer-containers'),
- pjaxContainer,
- pjaxOptions = {};
-
- if (pjax !== undefined && $.support.pjax) {
- if ($e.data('pjax-container')) {
- pjaxContainer = $e.data('pjax-container');
- } else {
- pjaxContainer = $e.closest('[data-pjax-container=""]');
- }
- // default to body if pjax container not found
- if (!pjaxContainer.length) {
- pjaxContainer = $('body');
- }
- pjaxOptions = {
- container: pjaxContainer,
- push: pjaxPushState,
- replace: pjaxReplaceState,
- scrollTo: pjaxScrollTo,
- pushRedirect: pjaxPushRedirect,
- replaceRedirect: pjaxReplaceRedirect,
- pjaxSkipOuterContainers: pjaxSkipOuterContainers,
- timeout: pjaxTimeout,
- originalEvent: event,
- originalTarget: $e
- }
- }
-
- if (method === undefined) {
- if (action && action != '#') {
- if (pjax !== undefined && $.support.pjax) {
- $.pjax.click(event, pjaxOptions);
- } else {
- window.location = action;
- }
- } else if ($e.is(':submit') && $form.length) {
- if (pjax !== undefined && $.support.pjax) {
- $form.on('submit',function(e){
- $.pjax.submit(e, pjaxOptions);
- })
- }
- $form.trigger('submit');
- }
- return;
- }
-
- var newForm = !$form.length;
- if (newForm) {
- if (!action || !action.match(/(^\/|:\/\/)/)) {
- action = window.location.href;
- }
- $form = $('<form/>', {method: method, action: action});
- var target = $e.attr('target');
- if (target) {
- $form.attr('target', target);
- }
- if (!method.match(/(get|post)/i)) {
- $form.append($('<input/>', {name: '_method', value: method, type: 'hidden'}));
- method = 'POST';
- }
- if (!method.match(/(get|head|options)/i)) {
- 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 who triggers the form submission. This is used by yii.activeForm.js
- activeFormData.submitObject = $e;
- }
-
- // temporarily add hidden inputs according to data-params
- if (params && $.isPlainObject(params)) {
- $.each(params, function (idx, obj) {
- $form.append($('<input/>').attr({name: idx, value: obj, type: 'hidden'}));
- });
- }
-
- var oldMethod = $form.attr('method');
- $form.attr('method', method);
- var oldAction = null;
- if (action && action != '#') {
- oldAction = $form.attr('action');
- $form.attr('action', action);
- }
- if (pjax !== undefined && $.support.pjax) {
- $form.on('submit',function(e){
- $.pjax.submit(e, pjaxOptions);
- })
- }
- $form.trigger('submit');
- $.when($form.data('yiiSubmitFinalizePromise')).then(
- function () {
- if (oldAction != null) {
- $form.attr('action', oldAction);
- }
- $form.attr('method', oldMethod);
-
- // remove the temporarily added hidden inputs
- if (params && $.isPlainObject(params)) {
- $.each(params, function (idx, obj) {
- $('input[name="' + idx + '"]', $form).remove();
- });
- }
-
- if (newForm) {
- $form.remove();
- }
- }
- );
- },
-
- getQueryParams: function (url) {
- var pos = url.indexOf('?');
- if (pos < 0) {
- return {};
- }
-
- var pairs = url.substring(pos + 1).split('#')[0].split('&'),
- params = {},
- pair,
- i;
-
- for (i = 0; i < pairs.length; i++) {
- pair = pairs[i].split('=');
- var name = decodeURIComponent(pair[0]);
- var value = decodeURIComponent(pair[1]);
- if (name.length) {
- if (params[name] !== undefined) {
- if (!$.isArray(params[name])) {
- params[name] = [params[name]];
- }
- params[name].push(value || '');
- } else {
- params[name] = value || '';
- }
- }
- }
- return params;
- },
-
- initModule: function (module) {
- if (module.isActive === undefined || module.isActive) {
- if ($.isFunction(module.init)) {
- module.init();
- }
- $.each(module, function () {
- if ($.isPlainObject(this)) {
- pub.initModule(this);
- }
- });
- }
- },
-
- init: function () {
- initCsrfHandler();
- initRedirectHandler();
- initScriptFilter();
- initDataMethods();
- }
- };
-
- function initRedirectHandler() {
- // handle AJAX redirection
- $(document).ajaxComplete(function (event, xhr, settings) {
- var url = xhr && xhr.getResponseHeader('X-Redirect');
- if (url) {
- window.location = url;
- }
- });
- }
-
- 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 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) {
- $.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 initScriptFilter() {
- var hostInfo = location.protocol + '//' + location.host;
-
- var loadedScripts = $('script[src]').map(function () {
- return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src;
- }).toArray();
-
- $.ajaxPrefilter('script', function (options, originalOptions, xhr) {
- if (options.dataType == 'jsonp') {
- return;
- }
-
- var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url;
- if ($.inArray(url, loadedScripts) === -1) {
- loadedScripts.push(url);
- } else {
- var isReloadable = $.inArray(url, $.map(pub.reloadableScripts, function (script) {
- return script.charAt(0) === '/' ? hostInfo + script : script;
- })) !== -1;
- if (!isReloadable) {
- xhr.abort();
- }
- }
- });
-
- $(document).ajaxComplete(function (event, xhr, settings) {
- var styleSheets = [];
- $('link[rel=stylesheet]').each(function () {
- if ($.inArray(this.href, pub.reloadableScripts) !== -1) {
- return;
- }
- if ($.inArray(this.href, styleSheets) == -1) {
- styleSheets.push(this.href)
- } else {
- $(this).remove();
- }
- })
- });
- }
-
- return pub;
- })(jQuery);
-
- jQuery(function () {
- yii.initModule(yii);
- });
|