web2py.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. (function ($, undefined) {
  2. /*
  3. * Unobtrusive scripting adapter for jQuery, largely taken from
  4. * the wonderful https://github.com/rails/jquery-ujs
  5. *
  6. *
  7. * Released under the MIT license
  8. *
  9. */
  10. 'use strict';
  11. if ($.web2py !== undefined) {
  12. $.error('web2py.js has already been loaded!');
  13. }
  14. String.prototype.reverse = function () {
  15. return this.split('').reverse().join('');
  16. };
  17. var web2py;
  18. $.web2py = web2py = {
  19. isUndefined: function (obj) {
  20. /* grabbed from underscore.js */
  21. return obj === void 0;
  22. },
  23. popup: function (url) {
  24. /* popup a window */
  25. var newwindow = window.open(url, 'name', 'height=400,width=600');
  26. if (window.focus) newwindow.focus();
  27. return false;
  28. },
  29. collapse: function (id) {
  30. /* toggle an element */
  31. $('#' + id).slideToggle();
  32. },
  33. fade: function (id, value) {
  34. /*fade something*/
  35. if (value > 0) $('#' + id).hide().fadeIn('slow');
  36. else $('#' + id).show().fadeOut('slow');
  37. },
  38. ajax: function (u, s, t) {
  39. /*simple ajax function*/
  40. var query = '';
  41. if (typeof s == 'string') {
  42. var d = $(s).serialize();
  43. if (d) {
  44. query = d;
  45. }
  46. } else {
  47. var pcs = [];
  48. if (s !== null && !web2py.isUndefined(s))
  49. for (var i = 0; i < s.length; i++) {
  50. var q = $('[name=' + s[i] + ']').serialize();
  51. if (q) {
  52. pcs.push(q);
  53. }
  54. }
  55. if (pcs.length > 0) {
  56. query = pcs.join('&');
  57. }
  58. }
  59. $.ajax({
  60. type: 'POST',
  61. url: u,
  62. data: query,
  63. success: function (msg) {
  64. if (t) {
  65. if (t == ':eval') eval(msg);
  66. else if (typeof t == 'string') $('#' + t).html(msg);
  67. else t(msg);
  68. }
  69. }
  70. });
  71. },
  72. ajax_fields: function (target) {
  73. /*
  74. *this attaches something to a newly loaded fragment/page
  75. * Ideally all events should be bound to the document, so we can avoid calling
  76. * this over and over... all will be bound to the document
  77. */
  78. /*adds btn class to buttons*/
  79. $('button:not([class^="btn"])', target).addClass('btn');
  80. $(
  81. 'form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])',
  82. target).addClass('btn');
  83. /* javascript for PasswordWidget*/
  84. $('input[type=password][data-w2p_entropy]', target).each(function () {
  85. web2py.validate_entropy($(this));
  86. });
  87. /* javascript for ListWidget*/
  88. $('ul.w2p_list', target).each(function () {
  89. function pe(ul, e) {
  90. var new_line = ml(ul);
  91. rel(ul);
  92. if ($(e.target).parent().is(':visible')) {
  93. /* make sure we didn't delete the element before we insert after */
  94. new_line.insertAfter($(e.target).parent());
  95. } else {
  96. /* the line we clicked on was deleted, just add to end of list */
  97. new_line.appendTo(ul);
  98. }
  99. new_line.find(':text').focus();
  100. return false;
  101. }
  102. function rl(ul, e) {
  103. if ($(ul).children().length > 1) {
  104. /* only remove if we have more than 1 item so the list is never empty */
  105. $(e.target).parent().remove();
  106. }
  107. }
  108. function ml(ul) {
  109. /* clone the first field */
  110. var line = $(ul).find('li:first').clone(true);
  111. line.find(':text').val('');
  112. return line;
  113. }
  114. function rel(ul) {
  115. /* keep only as many as needed*/
  116. $(ul).find('li').each(function () {
  117. var trimmed = $.trim($(this.firstChild).val());
  118. if (trimmed === '') $(this).remove();
  119. else $(this.firstChild).val(trimmed);
  120. });
  121. }
  122. var ul = this;
  123. $(ul).find(':text').after('<a href="#">+</a>&nbsp;<a href="#">-</a>').keypress(
  124. function (e) {
  125. return (e.which == 13) ? pe(ul, e) : true;
  126. }).next().click(function (e) {
  127. pe(ul, e);
  128. e.preventDefault();
  129. }).next().click(function (e) {
  130. rl(ul, e);
  131. e.preventDefault();
  132. });
  133. });
  134. },
  135. ajax_init: function (target) {
  136. /*called whenever a fragment gets loaded */
  137. $('.w2p_hidden', target).hide();
  138. web2py.manage_errors(target);
  139. web2py.ajax_fields(target);
  140. web2py.show_if_handler(target);
  141. web2py.component_handler(target);
  142. },
  143. /* manage errors in forms */
  144. manage_errors: function (target) {
  145. $('div.error', target).hide().slideDown('slow');
  146. },
  147. after_ajax: function (xhr) {
  148. /* called whenever an ajax request completes */
  149. var command = xhr.getResponseHeader('web2py-component-command');
  150. var flash = xhr.getResponseHeader('web2py-component-flash');
  151. if (command !== null) {
  152. eval(decodeURIComponent(command));
  153. }
  154. if (flash) {
  155. web2py.flash(decodeURIComponent(flash));
  156. }
  157. },
  158. event_handlers: function () {
  159. /*
  160. * This is called once for page
  161. * Ideally it should bound all the things that are needed
  162. * and require no dom manipulations
  163. */
  164. var doc = $(document);
  165. doc.on('click', '.w2p_flash', function () {
  166. var t = $(this);
  167. if (t.css('top') == '0px') t.slideUp('slow');
  168. else t.fadeOut();
  169. });
  170. doc.on('keyup', 'input.integer', function () {
  171. var nvalue = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse();
  172. if (this.value != nvalue) this.value = nvalue;
  173. });
  174. doc.on('keyup', 'input.double, input.decimal', function () {
  175. var nvalue = this.value.reverse().replace(
  176. /[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse();
  177. if (this.value != nvalue) this.value = nvalue;
  178. });
  179. var confirm_message = !web2py.isUndefined(w2p_ajax_confirm_message) ? w2p_ajax_confirm_message :
  180. 'Are you sure you want to delete this object?';
  181. doc.on('click', 'input[type="checkbox"].delete', function () {
  182. if (this.checked)
  183. if (!web2py.confirm(confirm_message)) this.checked = false;
  184. });
  185. var datetime_format = !web2py.isUndefined(w2p_ajax_datetime_format) ? w2p_ajax_datetime_format :
  186. '%Y-%m-%d %H:%M:%S';
  187. doc.on('click', 'input.datetime', function () {
  188. var tformat = $(this).data('w2p_datetime_format');
  189. var active = $(this).data('w2p_datetime');
  190. var format = !web2py.isUndefined(tformat) ? tformat : datetime_format;
  191. if (active === undefined) {
  192. Calendar.setup({
  193. inputField: this,
  194. ifFormat: format,
  195. showsTime: true,
  196. timeFormat: '24'
  197. });
  198. $(this).attr('autocomplete', 'off');
  199. $(this).data('w2p_datetime', 1);
  200. $(this).trigger('click');
  201. }
  202. });
  203. var date_format = !web2py.isUndefined(w2p_ajax_date_format) ? w2p_ajax_date_format : '%Y-%m-%d';
  204. doc.on('click', 'input.date', function () {
  205. var tformat = $(this).data('w2p_date_format');
  206. var active = $(this).data('w2p_date');
  207. var format = !web2py.isUndefined(tformat) ? tformat : date_format;
  208. if (active === undefined) {
  209. Calendar.setup({
  210. inputField: this,
  211. ifFormat: format,
  212. showsTime: false
  213. });
  214. $(this).data('w2p_date', 1);
  215. $(this).attr('autocomplete', 'off');
  216. $(this).trigger('click');
  217. }
  218. });
  219. doc.on('focus', 'input.time', function () {
  220. var active = $(this).data('w2p_time');
  221. if (web2py.isUndefined(active)) {
  222. $(this).timeEntry({
  223. spinnerImage: ''
  224. }).attr('autocomplete', 'off');
  225. $(this).data('w2p_time', 1);
  226. }
  227. });
  228. /* help preventing double form submission for normal form (not LOADed) */
  229. $(doc).on('submit', 'form', function () {
  230. var submit_button = $(this).find(web2py.formInputClickSelector);
  231. web2py.disableElement(submit_button);
  232. /* safeguard in case the form doesn't trigger a refresh,
  233. see https://github.com/web2py/web2py/issues/1100 */
  234. setTimeout(function () {
  235. web2py.enableElement(submit_button);
  236. }, 5000);
  237. });
  238. doc.ajaxSuccess(function (e, xhr) {
  239. var redirect = xhr.getResponseHeader('web2py-redirect-location');
  240. if (redirect !== null) {
  241. window.location = redirect;
  242. }
  243. /* run this here only if this Ajax request is NOT for a web2py component. */
  244. if (xhr.getResponseHeader('web2py-component-content') === null) {
  245. web2py.after_ajax(xhr);
  246. }
  247. });
  248. doc.ajaxError(function (e, xhr, settings, exception) {
  249. /*personally I don't like it.
  250. *if there's an error it it flashed and can be removed
  251. *as any other message
  252. *doc.off('click', '.w2p_flash')
  253. */
  254. switch (xhr.status) {
  255. case 500:
  256. web2py.flash(ajax_error_500);
  257. }
  258. });
  259. },
  260. trap_form: function (action, target) {
  261. /* traps any LOADed form */
  262. $('#' + target + ' form').each(function () {
  263. var form = $(this);
  264. if (form.hasClass('no_trap')) {
  265. return;
  266. }
  267. var w2p_target = $(this).attr('data-w2p_target');
  268. if (web2py.isUndefined(w2p_target) || w2p_target === false) {
  269. form.attr('data-w2p_target', target);
  270. } else {
  271. target = w2p_target;
  272. }
  273. var url = form.attr('action');
  274. if ((url === '') || (url === '#') || web2py.isUndefined(url)) {
  275. /* form has no action. Use component url. */
  276. url = action;
  277. }
  278. form.submit(function (e) {
  279. web2py.disableElement(form.find(web2py.formInputClickSelector));
  280. web2py.hide_flash();
  281. web2py.ajax_page('post', url, form.serialize(), target, form);
  282. e.preventDefault();
  283. });
  284. form.on('click', web2py.formInputClickSelector, function (e) {
  285. e.preventDefault();
  286. var input_name = $(this).attr('name');
  287. if (!web2py.isUndefined(input_name)) {
  288. $('<input type="hidden" />').attr('name', input_name)
  289. .attr('value', $(this).val()).appendTo(form);
  290. }
  291. form.trigger('submit');
  292. });
  293. });
  294. },
  295. ajax_page: function (method, action, data, target, element) {
  296. /* element is a new parameter, but should be put be put in front */
  297. if (web2py.isUndefined(element)) element = $(document);
  298. /* if target is not there, fill it with something that there isn't in the page*/
  299. if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
  300. if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
  301. $.ajax({
  302. 'type': method,
  303. 'url': action,
  304. 'data': data,
  305. 'beforeSend': function (xhr, settings) {
  306. xhr.setRequestHeader('web2py-component-location', document.location);
  307. xhr.setRequestHeader('web2py-component-element', target);
  308. return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
  309. },
  310. 'success': function (data, status, xhr) {
  311. /*bummer for form submissions....the element is not there after complete
  312. *because it gets replaced by the new response....
  313. */
  314. web2py.fire(element, 'ajax:success', [data, status, xhr], target);
  315. },
  316. 'error': function (xhr, status, error) {
  317. /*bummer for form submissions....in addition to the element being not there after
  318. *complete because it gets replaced by the new response, standard form
  319. *handling just returns the same status code for good and bad
  320. *form submissions (i.e. that triggered a validator error)
  321. */
  322. web2py.fire(element, 'ajax:error', [xhr, status, error], target);
  323. },
  324. 'complete': function (xhr, status) {
  325. web2py.fire(element, 'ajax:complete', [xhr, status], target);
  326. web2py.updatePage(xhr, target); /* Parse and load the html received */
  327. web2py.trap_form(action, target);
  328. web2py.ajax_init('#' + target);
  329. web2py.after_ajax(xhr);
  330. }
  331. });
  332. }
  333. },
  334. component: function (action, target, timeout, times, el) {
  335. /* element is a new parameter, but should be put in front */
  336. $(function () {
  337. var jelement = $('#' + target);
  338. var element = jelement.get(0);
  339. var statement = 'jQuery("#' + target + '").get(0).reload();';
  340. element.reload = function () {
  341. /* Continue if times is Infinity or
  342. * the times limit is not reached
  343. */
  344. if (element.reload_check()) {
  345. web2py.ajax_page('get', action, null, target, el);
  346. }
  347. };
  348. /* Method to check timing limit */
  349. element.reload_check = function () {
  350. if (jelement.hasClass('w2p_component_stop')) {
  351. clearInterval(this.timing);
  352. return false;
  353. }
  354. if (this.reload_counter == Infinity) {
  355. return true;
  356. } else {
  357. if (!isNaN(this.reload_counter)) {
  358. this.reload_counter -= 1;
  359. if (this.reload_counter < 0) {
  360. if (!this.run_once) {
  361. clearInterval(this.timing);
  362. return false;
  363. }
  364. } else {
  365. return true;
  366. }
  367. }
  368. }
  369. return false;
  370. };
  371. if (!isNaN(timeout)) {
  372. element.timeout = timeout;
  373. element.reload_counter = times;
  374. if (times > 1) {
  375. /* Multiple or infinite reload
  376. * Run first iteration
  377. */
  378. web2py.ajax_page('get', action, null, target, el);
  379. element.run_once = false;
  380. element.timing = setInterval(statement, timeout);
  381. element.reload_counter -= 1;
  382. } else if (times == 1) {
  383. /* Run once with timeout */
  384. element.run_once = true;
  385. element.setTimeout = setTimeout;
  386. element.timing = setTimeout(statement, timeout);
  387. }
  388. } else {
  389. /* run once (no timeout specified) */
  390. element.reload_counter = Infinity;
  391. web2py.ajax_page('get', action, null, target, el);
  392. }
  393. });
  394. },
  395. updatePage: function (xhr, target) {
  396. var t = $('#' + target);
  397. var html = $.parseHTML(xhr.responseText, document, true);
  398. var title_elements = $(html).filter('title').add($(html).find('title'));
  399. var title = title_elements.last().text();
  400. if (title) {
  401. title_elements.remove(); /* Remove any title elements from the response */
  402. document.title = $.trim(title); /* Set the new document title */
  403. }
  404. var content = xhr.getResponseHeader('web2py-component-content');
  405. if (content == 'prepend') t.prepend(xhr.responseText);
  406. else if (content == 'append') t.append(xhr.responseText);
  407. else if (content != 'hide') t.html(html);
  408. },
  409. calc_entropy: function (mystring) {
  410. /* calculate a simple entropy for a given string */
  411. var csets = new Array(
  412. 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
  413. '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
  414. '0123456789abcdefghijklmnopqrstuvwxyz');
  415. var score = 0,
  416. other = {},
  417. seen = {},
  418. lastset = null,
  419. mystringlist = mystring.split('');
  420. for (var i = 0; i < mystringlist.length; i++) { /* classify this character */
  421. var c = mystringlist[i],
  422. inset = 5;
  423. for (var j = 0; j < csets.length; j++)
  424. if (csets[j].indexOf(c) != -1) {
  425. inset = j;
  426. break;
  427. }
  428. /*calculate effect of character on alphabet size */
  429. if (!(inset in seen)) {
  430. seen[inset] = 1;
  431. score += csets[inset].length;
  432. } else if (!(c in other)) {
  433. score += 1;
  434. other[c] = 1;
  435. }
  436. if (inset != lastset) {
  437. score += 1;
  438. lastset = inset;
  439. }
  440. }
  441. var entropy = mystring.length * Math.log(score) / 0.6931471805599453;
  442. return Math.round(entropy * 100) / 100;
  443. },
  444. validate_entropy: function (myfield, req_entropy) {
  445. if (!web2py.isUndefined(myfield.data('w2p_entropy'))) req_entropy = myfield.data('w2p_entropy');
  446. var validator = function () {
  447. var v = (web2py.calc_entropy(myfield.val()) || 0) / req_entropy;
  448. var r = 0,
  449. g = 0,
  450. b = 0,
  451. rs = function (x) {
  452. return Math.round(x * 15).toString(16);
  453. };
  454. if (v <= 0.5) {
  455. r = 1.0;
  456. g = 2.0 * v;
  457. } else {
  458. r = (1.0 - 2.0 * (Math.max(v, 0) - 0.5));
  459. g = 1.0;
  460. }
  461. var color = '#' + rs(r) + rs(g) + rs(b);
  462. myfield.css('background-color', color);
  463. var entropy_callback = myfield.data('entropy_callback');
  464. if (entropy_callback) entropy_callback(v);
  465. };
  466. if (!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator)
  467. .addClass('entropy_check');
  468. },
  469. web2py_websocket: function (url, onmessage, onopen, onclose) {
  470. if ('WebSocket' in window) {
  471. var ws = new WebSocket(url);
  472. ws.onopen = onopen ? onopen : (function () {});
  473. ws.onmessage = onmessage;
  474. ws.onclose = onclose ? onclose : (function () {});
  475. return true; /* supported */
  476. } else return false; /* not supported */
  477. },
  478. /* new from here */
  479. /* Form input elements bound by web2py.js */
  480. formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])',
  481. /* Form input elements disabled during form submission */
  482. disableSelector: 'input, button, textarea, select',
  483. /* Form input elements re-enabled after form submission */
  484. enableSelector: 'input:disabled, button:disabled, textarea:disabled, select:disabled',
  485. /* Triggers an event on an element and returns false if the event result is false */
  486. fire: function (obj, type, data, target) {
  487. var event = $.Event(type, {
  488. 'containerTarget': $('#' + target)[0]
  489. });
  490. obj.trigger(event, data);
  491. return event.result !== false;
  492. },
  493. /* Helper function, needed to provide consistent behavior in IE */
  494. stopEverything: function (e) {
  495. $(e.target).trigger('w2p:everythingStopped');
  496. e.stopImmediatePropagation();
  497. return false;
  498. },
  499. confirm: function (message) {
  500. return confirm(message);
  501. },
  502. /* replace element's html with the 'data-disable-with' after storing original html
  503. * and prevent clicking on it */
  504. disableElement: function (el) {
  505. if (!web2py.isUndefined(el.data('w2p_disable'))) {
  506. return false;
  507. }
  508. el.addClass('disabled');
  509. var method = el.is('input') ? 'val' : 'html';
  510. //method = el.attr('name') ? 'html' : 'val';
  511. var disable_with_message = (!web2py.isUndefined(w2p_ajax_disable_with_message)) ?
  512. w2p_ajax_disable_with_message : 'Working...';
  513. /*store enabled state if not already disabled */
  514. if (web2py.isUndefined(el.data('w2p_enable_with'))) {
  515. el.data('w2p_enable_with', el[method]());
  516. }
  517. /*if you don't want to see "working..." on buttons, replace the following
  518. * two lines with this one
  519. * el.data('w2p_disable_with', el[method]());
  520. */
  521. if ((el.data('w2p_disable_with') == 'default') || (web2py.isUndefined(el.data(
  522. 'w2p_disable_with')))) {
  523. el.data('w2p_disable_with', disable_with_message);
  524. }
  525. /* set to disabled state*/
  526. el[method](el.data('w2p_disable_with'));
  527. el.bind('click.w2pDisable', function (e) { /* prevent further clicking*/
  528. return web2py.stopEverything(e);
  529. });
  530. },
  531. /* restore element to its original state which was disabled by 'disableElement' above*/
  532. enableElement: function (el) {
  533. var method = el.is('input') ? 'val' : 'html';
  534. if (!web2py.isUndefined(el.data('w2p_enable_with'))) {
  535. /* set to old enabled state */
  536. el[method](el.data('w2p_enable_with'));
  537. el.removeData('w2p_enable_with');
  538. }
  539. el.removeClass('disabled');
  540. el.unbind('click.w2pDisable');
  541. },
  542. /*convenience wrapper, internal use only */
  543. simple_component: function (action, target, element) {
  544. web2py.component(action, target, 0, 1, element);
  545. },
  546. /*helper for flash messages*/
  547. flash: function (message, status) {
  548. var flash = $('.w2p_flash');
  549. web2py.hide_flash();
  550. flash.html(message).addClass(status);
  551. if (flash.html()) flash.slideDown();
  552. },
  553. hide_flash: function () {
  554. $('.w2p_flash').fadeOut(0).html('');
  555. },
  556. show_if_handler: function (target) {
  557. var triggers = {};
  558. var show_if = function () {
  559. var t = $(this);
  560. var id = t.attr('id');
  561. t.attr('value', t.val());
  562. for (var k = 0; k < triggers[id].length; k++) {
  563. var dep = $('#' + triggers[id][k], target);
  564. var tr = $('#' + triggers[id][k] + '__row', target);
  565. if (t.is(dep.attr('data-show-if'))) tr.slideDown();
  566. else tr.hide();
  567. }
  568. };
  569. $('[data-show-trigger]', target).each(function () {
  570. var name = $(this).attr('data-show-trigger');
  571. // The field exists only when creating/editing a row
  572. if ($('#' + name).length) {
  573. if (!triggers[name]) triggers[name] = [];
  574. triggers[name].push($(this).attr('id'));
  575. }
  576. });
  577. for (var name in triggers) {
  578. $('#' + name, target).change(show_if).keyup(show_if);
  579. show_if.call($('#' + name, target));
  580. }
  581. },
  582. component_handler: function (target) {
  583. $('div[data-w2p_remote]', target).each(function () {
  584. var remote, times, timeout, target;
  585. var el = $(this);
  586. remote = el.data('w2p_remote');
  587. times = el.data('w2p_times');
  588. timeout = el.data('w2p_timeout');
  589. target = el.attr('id');
  590. web2py.component(remote, target, timeout, times, $(this));
  591. });
  592. },
  593. a_handler: function (el, e) {
  594. e.preventDefault();
  595. var method = el.data('w2p_method');
  596. var action = el.attr('href');
  597. var target = el.data('w2p_target');
  598. var confirm_message = el.data('w2p_confirm');
  599. var pre_call = el.data('w2p_pre_call');
  600. if (!web2py.isUndefined(pre_call)) {
  601. eval(pre_call);
  602. }
  603. if (confirm_message) {
  604. if (confirm_message == 'default') {
  605. confirm_message = w2p_ajax_confirm_message ||
  606. 'Are you sure you want to delete this object?';
  607. }
  608. if (!web2py.confirm(confirm_message)) {
  609. web2py.stopEverything(e);
  610. return;
  611. }
  612. }
  613. if (web2py.isUndefined(target)) {
  614. if (method == 'GET') {
  615. web2py.ajax_page('get', action, [], '', el);
  616. } else if (method == 'POST') {
  617. web2py.ajax_page('post', action, [], '', el);
  618. }
  619. } else {
  620. if (method == 'GET') {
  621. web2py.ajax_page('get', action, [], target, el);
  622. } else if (method == 'POST') {
  623. web2py.ajax_page('post', action, [], target, el);
  624. }
  625. }
  626. },
  627. a_handlers: function () {
  628. var el = $(document);
  629. el.on('click', 'a[data-w2p_method]', function (e) {
  630. web2py.a_handler($(this), e);
  631. });
  632. /* removal of element should happen only on success */
  633. el.on('ajax:success', 'a[data-w2p_method][data-w2p_remove]', function () {
  634. var el = $(this);
  635. var toremove = el.data('w2p_remove');
  636. if (!web2py.isUndefined(toremove)) {
  637. toremove = el.closest(toremove);
  638. if (!toremove.length) {
  639. /*this enables removal of whatever selector if a closest is not found */
  640. toremove = $(toremove);
  641. }
  642. toremove.remove();
  643. }
  644. });
  645. el.on('ajax:beforeSend', 'a[data-w2p_method][data-w2p_disable_with]', function () {
  646. web2py.disableElement($(this));
  647. });
  648. /*re-enable click on completion*/
  649. el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function () {
  650. web2py.enableElement($(this));
  651. });
  652. },
  653. /* Disables form elements:
  654. - Caches element value in 'w2p_enable_with' data store
  655. - Replaces element text with value of 'data-disable-with' attribute
  656. - Sets disabled property to true
  657. */
  658. disableFormElements: function (form) {
  659. form.find(web2py.disableSelector).each(function () {
  660. var element = $(this),
  661. method = element.is('button') ? 'html' : 'val';
  662. var disable_with = element.data('w2p_disable_with');
  663. var disable = element.data('w2p_disable');
  664. if (!web2py.isUndefined(disable)) {
  665. return false;
  666. }
  667. if (web2py.isUndefined(disable_with)) {
  668. element.data('w2p_disable_with', element[method]());
  669. }
  670. if (web2py.isUndefined(element.data('w2p_enable_with'))) {
  671. element.data('w2p_enable_with', element[method]());
  672. }
  673. element[method](element.data('w2p_disable_with'));
  674. element.prop('disabled', true);
  675. });
  676. },
  677. /* Re-enables disabled form elements:
  678. - Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
  679. - Sets disabled property to false
  680. */
  681. enableFormElements: function (form) {
  682. form.find(web2py.enableSelector).each(function () {
  683. var element = $(this),
  684. method = element.is('button') ? 'html' : 'val';
  685. if (element.data('w2p_enable_with')) {
  686. element[method](element.data('w2p_enable_with'));
  687. element.removeData('w2p_enable_with');
  688. }
  689. element.prop('disabled', false);
  690. });
  691. },
  692. form_handlers: function () {
  693. var el = $(document);
  694. el.on('ajax:beforeSend', 'form[data-w2p_target]', function () {
  695. web2py.disableFormElements($(this));
  696. });
  697. el.on('ajax:complete', 'form[data-w2p_target]', function () {
  698. web2py.enableFormElements($(this));
  699. });
  700. },
  701. /* Invalidate and force reload of a web2py component
  702. */
  703. invalidate: function (target) {
  704. $('div[data-w2p_remote]', target).each(function () {
  705. var el = $('#' + $(this).attr('id')).get(0);
  706. if (!web2py.isUndefined(el.timing)) { // Block triggering regular routines
  707. clearInterval(el.timing);
  708. }
  709. });
  710. $.web2py.component_handler(target);
  711. },
  712. main_hook: function () {
  713. var flash = $('.w2p_flash');
  714. flash.hide();
  715. if (flash.html()) web2py.flash(flash.html());
  716. web2py.ajax_init(document);
  717. web2py.event_handlers();
  718. web2py.a_handlers();
  719. web2py.form_handlers();
  720. }
  721. };
  722. /*end of functions */
  723. /*main hook*/
  724. $(function () {
  725. web2py.main_hook();
  726. });
  727. })(jQuery);
  728. /* compatibility code - start */
  729. ajax = jQuery.web2py.ajax;
  730. web2py_component = jQuery.web2py.component;
  731. web2py_websocket = jQuery.web2py.web2py_websocket;
  732. web2py_ajax_page = jQuery.web2py.ajax_page;
  733. /*needed for IS_STRONG(entropy)*/
  734. web2py_validate_entropy = jQuery.web2py.validate_entropy;
  735. /*needed for crud.search and SQLFORM.grid's search*/
  736. web2py_ajax_fields = jQuery.web2py.ajax_fields;
  737. /*used for LOAD(ajax=False)*/
  738. web2py_trap_form = jQuery.web2py.trap_form;
  739. /*undocumented - rare*/
  740. popup = jQuery.web2py.popup;
  741. collapse = jQuery.web2py.collapse;
  742. fade = jQuery.web2py.fade;
  743. /* internals - shouldn't be needed
  744. web2py_ajax_init = jQuery.web2py.ajax_init;
  745. web2py_event_handlers = jQuery.web2py.event_handlers;
  746. web2py_trap_link = jQuery.web2py.trap_link;
  747. web2py_calc_entropy = jQuery.web2py.calc_entropy;
  748. */
  749. /* compatibility code - end*/