web2py.js 28 KB

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