/** * Yii JavaScript module. * * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ * @author Qiang Xue * @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 method = $e.data('method'), $form = $e.closest('form'), 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 = $('
', {method: method, action: action}); var target = $e.attr('target'); if (target) { $form.attr('target', target); } if (!method.match(/(get|post)/i)) { $form.append($('', {name: '_method', value: method, type: 'hidden'})); method = 'POST'; } if (!method.match(/(get|head|options)/i)) { var csrfParam = pub.getCsrfParam(); if (csrfParam) { $form.append($('', {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($('').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('&'), 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.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'); if (method === undefined && message === 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(document).ready(function () { yii.initModule(yii); });