Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

418 lines
16KB

  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 <qiang.xue@gmail.com>
  8. * @since 2.0
  9. */
  10. /**
  11. * yii is the root module for all Yii JavaScript modules.
  12. * It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()".
  13. *
  14. * Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii").
  15. *
  16. * A module may be structured as follows:
  17. *
  18. * ```javascript
  19. * yii.sample = (function($) {
  20. * var pub = {
  21. * // whether this module is currently active. If false, init() will not be called for this module
  22. * // it will also not be called for all its child modules. If this property is undefined, it means true.
  23. * isActive: true,
  24. * init: function() {
  25. * // ... module initialization code go here ...
  26. * },
  27. *
  28. * // ... other public functions and properties go here ...
  29. * };
  30. *
  31. * // ... private functions and properties go here ...
  32. *
  33. * return pub;
  34. * })(jQuery);
  35. * ```
  36. *
  37. * Using this structure, you can define public and private functions/properties for a module.
  38. * Private functions/properties are only visible within the module, while public functions/properties
  39. * may be accessed outside of the module. For example, you can access "yii.sample.isActive".
  40. *
  41. * You must call "yii.initModule()" once for the root module of all your modules.
  42. */
  43. yii = (function ($) {
  44. var pub = {
  45. /**
  46. * List of JS or CSS URLs that can be loaded multiple times via AJAX requests. Each script can be represented
  47. * as either an absolute URL or a relative one.
  48. */
  49. reloadableScripts: [],
  50. /**
  51. * The selector for clickable elements that need to support confirmation and form submission.
  52. */
  53. clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]',
  54. /**
  55. * The selector for changeable elements that need to support confirmation and form submission.
  56. */
  57. changeableSelector: 'select, input, textarea',
  58. /**
  59. * @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled.
  60. */
  61. getCsrfParam: function () {
  62. return $('meta[name=csrf-param]').attr('content');
  63. },
  64. /**
  65. * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled.
  66. */
  67. getCsrfToken: function () {
  68. return $('meta[name=csrf-token]').attr('content');
  69. },
  70. /**
  71. * Sets the CSRF token in the meta elements.
  72. * This method is provided so that you can update the CSRF token with the latest one you obtain from the server.
  73. * @param name the CSRF token name
  74. * @param value the CSRF token value
  75. */
  76. setCsrfToken: function (name, value) {
  77. $('meta[name=csrf-param]').attr('content', name);
  78. $('meta[name=csrf-token]').attr('content', value);
  79. },
  80. /**
  81. * Updates all form CSRF input fields with the latest CSRF token.
  82. * This method is provided to avoid cached forms containing outdated CSRF tokens.
  83. */
  84. refreshCsrfToken: function () {
  85. var token = pub.getCsrfToken();
  86. if (token) {
  87. $('form input[name="' + pub.getCsrfParam() + '"]').val(token);
  88. }
  89. },
  90. /**
  91. * Displays a confirmation dialog.
  92. * The default implementation simply displays a js confirmation dialog.
  93. * You may override this by setting `yii.confirm`.
  94. * @param message the confirmation message.
  95. * @param ok a callback to be called when the user confirms the message
  96. * @param cancel a callback to be called when the user cancels the confirmation
  97. */
  98. confirm: function (message, ok, cancel) {
  99. if (confirm(message)) {
  100. !ok || ok();
  101. } else {
  102. !cancel || cancel();
  103. }
  104. },
  105. /**
  106. * Handles the action triggered by user.
  107. * This method recognizes the `data-method` attribute of the element. If the attribute exists,
  108. * the method will submit the form containing this element. If there is no containing form, a form
  109. * will be created and submitted using the method given by this attribute value (e.g. "post", "put").
  110. * For hyperlinks, the form action will take the value of the "href" attribute of the link.
  111. * For other elements, either the containing form action or the current page URL will be used
  112. * as the form action URL.
  113. *
  114. * If the `data-method` attribute is not defined, the `href` attribute (if any) of the element
  115. * will be assigned to `window.location`.
  116. *
  117. * Starting from version 2.0.3, the `data-params` attribute is also recognized when you specify
  118. * `data-method`. The value of `data-params` should be a JSON representation of the data (name-value pairs)
  119. * that should be submitted as hidden inputs. For example, you may use the following code to generate
  120. * such a link:
  121. *
  122. * ```php
  123. * use yii\helpers\Html;
  124. * use yii\helpers\Json;
  125. *
  126. * echo Html::a('submit', ['site/foobar'], [
  127. * 'data' => [
  128. * 'method' => 'post',
  129. * 'params' => [
  130. * 'name1' => 'value1',
  131. * 'name2' => 'value2',
  132. * ],
  133. * ],
  134. * ];
  135. * ```
  136. *
  137. * @param $e the jQuery representation of the element
  138. */
  139. handleAction: function ($e, event) {
  140. var $form = $e.attr('data-form') ? $('#' + $e.attr('data-form')) : $e.closest('form'),
  141. method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'),
  142. action = $e.attr('href'),
  143. params = $e.data('params'),
  144. pjax = $e.data('pjax'),
  145. pjaxPushState = !!$e.data('pjax-push-state'),
  146. pjaxReplaceState = !!$e.data('pjax-replace-state'),
  147. pjaxTimeout = $e.data('pjax-timeout'),
  148. pjaxScrollTo = $e.data('pjax-scrollto'),
  149. pjaxPushRedirect = $e.data('pjax-push-redirect'),
  150. pjaxReplaceRedirect = $e.data('pjax-replace-redirect'),
  151. pjaxSkipOuterContainers = $e.data('pjax-skip-outer-containers'),
  152. pjaxContainer,
  153. pjaxOptions = {};
  154. if (pjax !== undefined && $.support.pjax) {
  155. if ($e.data('pjax-container')) {
  156. pjaxContainer = $e.data('pjax-container');
  157. } else {
  158. pjaxContainer = $e.closest('[data-pjax-container=""]');
  159. }
  160. // default to body if pjax container not found
  161. if (!pjaxContainer.length) {
  162. pjaxContainer = $('body');
  163. }
  164. pjaxOptions = {
  165. container: pjaxContainer,
  166. push: pjaxPushState,
  167. replace: pjaxReplaceState,
  168. scrollTo: pjaxScrollTo,
  169. pushRedirect: pjaxPushRedirect,
  170. replaceRedirect: pjaxReplaceRedirect,
  171. pjaxSkipOuterContainers: pjaxSkipOuterContainers,
  172. timeout: pjaxTimeout,
  173. originalEvent: event,
  174. originalTarget: $e
  175. }
  176. }
  177. if (method === undefined) {
  178. if (action && action != '#') {
  179. if (pjax !== undefined && $.support.pjax) {
  180. $.pjax.click(event, pjaxOptions);
  181. } else {
  182. window.location = action;
  183. }
  184. } else if ($e.is(':submit') && $form.length) {
  185. if (pjax !== undefined && $.support.pjax) {
  186. $form.on('submit',function(e){
  187. $.pjax.submit(e, pjaxOptions);
  188. })
  189. }
  190. $form.trigger('submit');
  191. }
  192. return;
  193. }
  194. var newForm = !$form.length;
  195. if (newForm) {
  196. if (!action || !action.match(/(^\/|:\/\/)/)) {
  197. action = window.location.href;
  198. }
  199. $form = $('<form/>', {method: method, action: action});
  200. var target = $e.attr('target');
  201. if (target) {
  202. $form.attr('target', target);
  203. }
  204. if (!method.match(/(get|post)/i)) {
  205. $form.append($('<input/>', {name: '_method', value: method, type: 'hidden'}));
  206. method = 'POST';
  207. }
  208. if (!method.match(/(get|head|options)/i)) {
  209. var csrfParam = pub.getCsrfParam();
  210. if (csrfParam) {
  211. $form.append($('<input/>', {name: csrfParam, value: pub.getCsrfToken(), type: 'hidden'}));
  212. }
  213. }
  214. $form.hide().appendTo('body');
  215. }
  216. var activeFormData = $form.data('yiiActiveForm');
  217. if (activeFormData) {
  218. // remember who triggers the form submission. This is used by yii.activeForm.js
  219. activeFormData.submitObject = $e;
  220. }
  221. // temporarily add hidden inputs according to data-params
  222. if (params && $.isPlainObject(params)) {
  223. $.each(params, function (idx, obj) {
  224. $form.append($('<input/>').attr({name: idx, value: obj, type: 'hidden'}));
  225. });
  226. }
  227. var oldMethod = $form.attr('method');
  228. $form.attr('method', method);
  229. var oldAction = null;
  230. if (action && action != '#') {
  231. oldAction = $form.attr('action');
  232. $form.attr('action', action);
  233. }
  234. if (pjax !== undefined && $.support.pjax) {
  235. $form.on('submit',function(e){
  236. $.pjax.submit(e, pjaxOptions);
  237. })
  238. }
  239. $form.trigger('submit');
  240. $.when($form.data('yiiSubmitFinalizePromise')).then(
  241. function () {
  242. if (oldAction != null) {
  243. $form.attr('action', oldAction);
  244. }
  245. $form.attr('method', oldMethod);
  246. // remove the temporarily added hidden inputs
  247. if (params && $.isPlainObject(params)) {
  248. $.each(params, function (idx, obj) {
  249. $('input[name="' + idx + '"]', $form).remove();
  250. });
  251. }
  252. if (newForm) {
  253. $form.remove();
  254. }
  255. }
  256. );
  257. },
  258. getQueryParams: function (url) {
  259. var pos = url.indexOf('?');
  260. if (pos < 0) {
  261. return {};
  262. }
  263. var pairs = url.substring(pos + 1).split('#')[0].split('&'),
  264. params = {},
  265. pair,
  266. i;
  267. for (i = 0; i < pairs.length; i++) {
  268. pair = pairs[i].split('=');
  269. var name = decodeURIComponent(pair[0]);
  270. var value = decodeURIComponent(pair[1]);
  271. if (name.length) {
  272. if (params[name] !== undefined) {
  273. if (!$.isArray(params[name])) {
  274. params[name] = [params[name]];
  275. }
  276. params[name].push(value || '');
  277. } else {
  278. params[name] = value || '';
  279. }
  280. }
  281. }
  282. return params;
  283. },
  284. initModule: function (module) {
  285. if (module.isActive === undefined || module.isActive) {
  286. if ($.isFunction(module.init)) {
  287. module.init();
  288. }
  289. $.each(module, function () {
  290. if ($.isPlainObject(this)) {
  291. pub.initModule(this);
  292. }
  293. });
  294. }
  295. },
  296. init: function () {
  297. initCsrfHandler();
  298. initRedirectHandler();
  299. initScriptFilter();
  300. initDataMethods();
  301. }
  302. };
  303. function initRedirectHandler() {
  304. // handle AJAX redirection
  305. $(document).ajaxComplete(function (event, xhr, settings) {
  306. var url = xhr && xhr.getResponseHeader('X-Redirect');
  307. if (url) {
  308. window.location = url;
  309. }
  310. });
  311. }
  312. function initCsrfHandler() {
  313. // automatically send CSRF token for all AJAX requests
  314. $.ajaxPrefilter(function (options, originalOptions, xhr) {
  315. if (!options.crossDomain && pub.getCsrfParam()) {
  316. xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
  317. }
  318. });
  319. pub.refreshCsrfToken();
  320. }
  321. function initDataMethods() {
  322. var handler = function (event) {
  323. var $this = $(this),
  324. method = $this.data('method'),
  325. message = $this.data('confirm'),
  326. form = $this.data('form');
  327. if (method === undefined && message === undefined && form === undefined) {
  328. return true;
  329. }
  330. if (message !== undefined) {
  331. $.proxy(pub.confirm, this)(message, function () {
  332. pub.handleAction($this, event);
  333. });
  334. } else {
  335. pub.handleAction($this, event);
  336. }
  337. event.stopImmediatePropagation();
  338. return false;
  339. };
  340. // handle data-confirm and data-method for clickable and changeable elements
  341. $(document).on('click.yii', pub.clickableSelector, handler)
  342. .on('change.yii', pub.changeableSelector, handler);
  343. }
  344. function initScriptFilter() {
  345. var hostInfo = location.protocol + '//' + location.host;
  346. var loadedScripts = $('script[src]').map(function () {
  347. return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src;
  348. }).toArray();
  349. $.ajaxPrefilter('script', function (options, originalOptions, xhr) {
  350. if (options.dataType == 'jsonp') {
  351. return;
  352. }
  353. var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url;
  354. if ($.inArray(url, loadedScripts) === -1) {
  355. loadedScripts.push(url);
  356. } else {
  357. var isReloadable = $.inArray(url, $.map(pub.reloadableScripts, function (script) {
  358. return script.charAt(0) === '/' ? hostInfo + script : script;
  359. })) !== -1;
  360. if (!isReloadable) {
  361. xhr.abort();
  362. }
  363. }
  364. });
  365. $(document).ajaxComplete(function (event, xhr, settings) {
  366. var styleSheets = [];
  367. $('link[rel=stylesheet]').each(function () {
  368. if ($.inArray(this.href, pub.reloadableScripts) !== -1) {
  369. return;
  370. }
  371. if ($.inArray(this.href, styleSheets) == -1) {
  372. styleSheets.push(this.href)
  373. } else {
  374. $(this).remove();
  375. }
  376. })
  377. });
  378. }
  379. return pub;
  380. })(jQuery);
  381. jQuery(function () {
  382. yii.initModule(yii);
  383. });