yii.js 9.97 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7 8 9
/**
 * 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
 */
Qiang Xue committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

/**
 * 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:
 *
 * ~~~
 * 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;
Qiang Xue committed
35
 * })(jQuery);
Qiang Xue committed
36 37 38 39
 * ~~~
 *
 * 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
40
 * may be accessed outside of the module. For example, you can access "yii.sample.isActive".
Qiang Xue committed
41 42 43
 *
 * You must call "yii.initModule()" once for the root module of all your modules.
 */
Qiang Xue committed
44
yii = (function ($) {
Qiang Xue committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58
    var pub = {
        /**
         * List of scripts 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',
59

Qiang Xue committed
60 61 62 63 64 65
        /**
         * @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled.
         */
        getCsrfParam: function () {
            return $('meta[name=csrf-param]').prop('content');
        },
66

Qiang Xue committed
67 68 69 70 71 72
        /**
         * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled.
         */
        getCsrfToken: function () {
            return $('meta[name=csrf-token]').prop('content');
        },
73

Qiang Xue committed
74 75 76 77 78 79 80 81 82 83
        /**
         * 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.
         * @return boolean whether the user confirms with the message in the dialog
         */
        confirm: function (message) {
            return confirm(message);
        },
84

Qiang Xue committed
85 86 87 88 89 90 91 92 93 94 95 96
        /**
         * Returns a value indicating whether to allow executing the action defined for the specified element.
         * This method recognizes the `data-confirm` attribute of the element and uses it
         * as the message in a confirmation dialog. The method will return true if this special attribute
         * is not defined or if the user confirms the message.
         * @param $e the jQuery representation of the element
         * @return boolean whether to allow executing the action defined for the specified element.
         */
        allowAction: function ($e) {
            var message = $e.data('confirm');
            return message === undefined || pub.confirm(message);
        },
97

Qiang Xue committed
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
        /**
         * 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 default element action will be performed.
         *
         * @param $e the jQuery representation of the element
         * @return boolean whether to execute the default action for the element.
         */
        handleAction: function ($e) {
            var method = $e.data('method');
            if (method === undefined) {
                return true;
            }
117

Qiang Xue committed
118
            var $form = $e.closest('form');
Qiang Xue committed
119 120
            var action = $e.prop('href');
            var newForm = !$form.length || action;
Qiang Xue committed
121 122 123 124 125 126 127 128 129 130 131
            if (newForm) {
                if (!action || !action.match(/(^\/|:\/\/)/)) {
                    action = window.location.href;
                }
                $form = $('<form method="' + method + '" action="' + action + '"></form>');
                var target = $e.prop('target');
                if (target) {
                    $form.attr('target', target);
                }
                if (!method.match(/(get|post)/i)) {
                    $form.append('<input name="_method" value="' + method + '" type="hidden">');
Qiang Xue committed
132
                    method = 'POST';
Qiang Xue committed
133
                }
Qiang Xue committed
134 135 136 137 138
                if (!method.match(/(get|head|options)/i)) {
                    var csrfParam = pub.getCsrfParam();
                    if (csrfParam) {
                        $form.append('<input name="' + csrfParam + '" value="' + pub.getCsrfToken() + '" type="hidden">');
                    }
Qiang Xue committed
139 140 141
                }
                $form.hide().appendTo('body');
            }
142

Qiang Xue committed
143 144 145 146 147
            var activeFormData = $form.data('yiiActiveForm');
            if (activeFormData) {
                // remember who triggers the form submission. This is used by yii.activeForm.js
                activeFormData.submitObject = $e;
            }
148

Qiang Xue committed
149 150 151
            var oldMethod = $form.prop('method');
            $form.prop('method', method);

Qiang Xue committed
152
            $form.trigger('submit');
153

Qiang Xue committed
154 155
            $form.prop('method', oldMethod);

Qiang Xue committed
156 157 158
            if (newForm) {
                $form.remove();
            }
159

Qiang Xue committed
160 161
            return false;
        },
162

Qiang Xue committed
163 164 165 166 167 168 169 170 171 172 173 174
        getQueryParams: function (url) {
            var pos = url.indexOf('?');
            if (pos < 0) {
                return {};
            }
            var qs = url.substring(pos + 1).split('&');
            for (var i = 0, result = {}; i < qs.length; i++) {
                qs[i] = qs[i].split('=');
                result[decodeURIComponent(qs[i][0])] = decodeURIComponent(qs[i][1]);
            }
            return result;
        },
175

Qiang Xue committed
176 177 178 179 180 181 182 183 184 185 186 187
        initModule: function (module) {
            if (module.isActive === undefined || module.isActive) {
                if ($.isFunction(module.init)) {
                    module.init();
                }
                $.each(module, function () {
                    if ($.isPlainObject(this)) {
                        pub.initModule(this);
                    }
                });
            }
        },
188

Qiang Xue committed
189 190 191 192 193 194 195
        init: function () {
            initCsrfHandler();
            initRedirectHandler();
            initScriptFilter();
            initDataMethods();
        }
    };
196

Qiang Xue committed
197 198 199 200 201 202 203 204 205
    function initRedirectHandler() {
        // handle AJAX redirection
        $(document).ajaxComplete(function (event, xhr, settings) {
            var url = xhr.getResponseHeader('X-Redirect');
            if (url) {
                window.location = url;
            }
        });
    }
206

Qiang Xue committed
207 208 209 210 211 212 213 214
    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());
            }
        });
    }
215

Qiang Xue committed
216 217 218 219 220 221 222 223 224 225 226 227
    function initDataMethods() {
        var $document = $(document);
        // handle data-confirm and data-method for clickable elements
        $document.on('click.yii', pub.clickableSelector, function (event) {
            var $this = $(this);
            if (pub.allowAction($this)) {
                return pub.handleAction($this);
            } else {
                event.stopImmediatePropagation();
                return false;
            }
        });
228

Qiang Xue committed
229 230 231 232 233 234 235 236 237 238 239
        // handle data-confirm and data-method for changeable elements
        $document.on('change.yii', pub.changeableSelector, function (event) {
            var $this = $(this);
            if (pub.allowAction($this)) {
                return pub.handleAction($this);
            } else {
                event.stopImmediatePropagation();
                return false;
            }
        });
    }
240

Qiang Xue committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
    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 found = $.inArray(url, $.map(pub.reloadableScripts, function (script) {
                    return script.charAt(0) === '/' ? hostInfo + script : script;
                })) !== -1;
                if (!found) {
                    xhr.abort();
                }
            }
        });
    }
263

Qiang Xue committed
264
    return pub;
Qiang Xue committed
265
})(jQuery);
Qiang Xue committed
266

Qiang Xue committed
267
jQuery(document).ready(function () {
Qiang Xue committed
268
    yii.initModule(yii);
Qiang Xue committed
269
});