Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

741 linhas
19 KiB

  1. wn.ui.form.make_control = function(opts) {
  2. var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, "");
  3. if(wn.ui.form[control_class_name]) {
  4. return new wn.ui.form[control_class_name](opts);
  5. } else {
  6. console.log("Invalid Control Name: " + opts.df.fieldtype);
  7. }
  8. }
  9. // old style
  10. function make_field(docfield, doctype, parent, frm, in_grid, hide_label) { // Factory
  11. return new wn.ui.form.make_control({
  12. df: docfield,
  13. doctype: doctype,
  14. parent: parent,
  15. hide_label: hide_label,
  16. frm: frm
  17. });
  18. }
  19. wn.ui.form.Control = Class.extend({
  20. init: function(opts) {
  21. $.extend(this, opts);
  22. this.make();
  23. },
  24. make: function() {
  25. this.$wrapper = $("<div>").appendTo(this.parent);
  26. this.wrapper = this.$wrapper.get(0);
  27. },
  28. // returns "Read", "Write" or "None"
  29. // as strings based on permissions
  30. get_status: function(explain) {
  31. if(!this.doctype)
  32. return "Write";
  33. return wn.perm.get_field_display_status(this.df,
  34. locals[this.doctype][this.docname], this.perm, explain);
  35. },
  36. refresh: function() {
  37. this.disp_status = this.get_status();
  38. this.$wrapper
  39. && this.$wrapper.toggle(this.disp_status!="None")
  40. && this.$wrapper.trigger("refresh");
  41. },
  42. get_doc: function() {
  43. return this.doctype && this.docname
  44. && locals[this.doctype] && locals[this.doctype][this.docname] || {};
  45. },
  46. parse_validate_and_set_in_model: function(value) {
  47. var me = this;
  48. this.inside_change_event = true;
  49. if(this.parse) value = this.parse(value);
  50. var set = function(value) {
  51. me.set_model_value(value);
  52. me.inside_change_event = false;
  53. }
  54. this.validate ? this.validate(value, set) : set(value);
  55. },
  56. set_model_value: function(value) {
  57. if(this.last_value!==value) {
  58. wn.model.set_value(this.doctype, this.docname, this.df.fieldname, value);
  59. this.frm && this.frm.dirty();
  60. this.last_value = value;
  61. }
  62. },
  63. });
  64. wn.ui.form.ControlHTML = wn.ui.form.Control.extend({
  65. make: function() {
  66. this._super();
  67. var me = this;
  68. this.disp_area = this.wrapper;
  69. this.$wrapper.on("refresh", function() {
  70. if(me.df.options)
  71. me.$wrapper.html(me.df.options);
  72. })
  73. }
  74. });
  75. wn.ui.form.ControlImage = wn.ui.form.Control.extend({
  76. make: function() {
  77. this._super();
  78. var me = this;
  79. this.$wrapper.on("refresh", function() {
  80. me.$wrapper.empty();
  81. if(me.df.options && me.frm.doc[me.df.options]) {
  82. $("<img src='"+me.frm.doc[me.df.options]+"' style='max-width: 70%;'>")
  83. .appendTo(me.$wrapper);
  84. } else {
  85. $("<div class='missing-image'><i class='icon-camera'></i></div>")
  86. .appendTo(me.$wrapper)
  87. }
  88. return false;
  89. })
  90. }
  91. });
  92. wn.ui.form.ControlReadOnly = wn.ui.form.Control.extend({
  93. make: function() {
  94. this._super();
  95. var me = this;
  96. this.$wrapper.on("refresh", function() {
  97. var value = wn.model.get_value(me.doctype, me.docname, me.fieldname);
  98. me.$wrapper.html(value);
  99. })
  100. }
  101. });
  102. wn.ui.form.ControlInput = wn.ui.form.Control.extend({
  103. make: function() {
  104. // parent element
  105. this.make_wrapper();
  106. this.wrapper = this.$wrapper.get(0);
  107. this.wrapper.fieldobj = this; // reference for event handlers
  108. this.set_input_areas();
  109. // set description
  110. this.set_max_width();
  111. this.setup_update_on_refresh();
  112. },
  113. make_wrapper: function() {
  114. if(this.only_input) {
  115. this.$wrapper = $("<span>").appendTo(this.parent);
  116. } else {
  117. this.$wrapper = $('<div class="control-group">\
  118. <label class="control-label"></label>\
  119. <div class="controls">\
  120. <div class="control-input"></div>\
  121. <div class="control-value like-disabled-input" style="display: none;"></div>\
  122. </div>\
  123. </div>').appendTo(this.parent);
  124. }
  125. },
  126. set_input_areas: function() {
  127. if(this.only_input) {
  128. this.input_area = this.wrapper;
  129. } else {
  130. this.label_area = this.label_span = this.$wrapper.find("label").get(0);
  131. this.input_area = this.$wrapper.find(".control-input").get(0);
  132. this.disp_area = this.$wrapper.find(".control-value").get(0);
  133. }
  134. },
  135. set_max_width: function() {
  136. if(['Code', 'Text Editor', 'Text', 'Small Text', 'Table', 'HTML']
  137. .indexOf(this.df.fieldtype)==-1) {
  138. this.$wrapper.css({"max-width": "600px"});
  139. }
  140. },
  141. // update input value, label, description
  142. // display (show/hide/read-only),
  143. // mandatory style on refresh
  144. setup_update_on_refresh: function() {
  145. var me = this;
  146. this.$wrapper.on("refresh", function() {
  147. if(me.disp_status != "None") {
  148. // refresh value
  149. if(me.doctype && me.docname) {
  150. me.value = wn.model.get_value(me.doctype, me.docname, me.df.fieldname);
  151. }
  152. if(me.disp_status=="Write") {
  153. me.disp_area && $(me.disp_area).toggle(false);
  154. $(me.input_area).toggle(true);
  155. !me.has_input && me.make_input();
  156. if(me.doctype && me.docname)
  157. me.set_input(me.value);
  158. } else {
  159. $(me.input_area).toggle(false);
  160. me.disp_area && $(me.disp_area)
  161. .toggle(true)
  162. .html(
  163. wn.format(me.value, me.df, null, locals[me.doctype][me.name])
  164. );
  165. }
  166. me.set_description();
  167. me.set_label();
  168. me.set_mandatory();
  169. return false;
  170. }
  171. })
  172. },
  173. set_label: function() {
  174. if(this.only_input || this.df.label==this._label)
  175. return;
  176. this.label_span.innerHTML = wn._(this.df.label);
  177. this._label = this.df.label;
  178. },
  179. set_description: function() {
  180. if(this.only_input || this.df.description==this._description)
  181. return;
  182. if(this.df.description) {
  183. if(!this.$wrapper.find(".help-box").length) {
  184. $('<p class="help-box small text-muted"></p>').appendTo(this.input_area);
  185. }
  186. this.$wrapper.find(".help-box").html(this.df.description);
  187. } else {
  188. this.$wrapper.find(".help-box").empty().toggle(false);
  189. }
  190. this._description = this.df.description;
  191. },
  192. set_mandatory: function() {
  193. this.$wrapper.toggleClass("has-error", (this.df.reqd
  194. && (this.value==null || this.value=="")) ? true : false);
  195. },
  196. });
  197. wn.ui.form.ControlData = wn.ui.form.ControlInput.extend({
  198. html_element: "input",
  199. input_type: "text",
  200. make_input: function() {
  201. this.$input = $("<"+ this.html_element +">")
  202. .attr("type", this.input_type)
  203. .addClass("col-span-12")
  204. .prependTo(this.input_area)
  205. this.set_input_attributes();
  206. this.input = this.$input.get(0);
  207. this.has_input = true;
  208. this.bind_change_event();
  209. },
  210. set_input_attributes: function() {
  211. this.$input
  212. .attr("data-fieldtype", this.df.fieldtype)
  213. .attr("data-fieldname", this.df.fieldname)
  214. .attr("placeholder", this.df.placeholder || "")
  215. if(this.doctype)
  216. this.$input.attr("data-doctype", this.doctype);
  217. },
  218. bind_change_event: function() {
  219. var me = this;
  220. this.$input.on("change", this.change || function() {
  221. me.doctype && me.docname && me.get_value
  222. && me.parse_validate_and_set_in_model(me.get_value()); } );
  223. },
  224. set_input: function(val) {
  225. this.$input.val(this.format_for_input(val));
  226. this.last_value = val;
  227. },
  228. get_value: function() {
  229. return this.$input.val();
  230. },
  231. format_for_input: function(val) {
  232. return val==null ? "" : val;
  233. },
  234. validate: function(v, callback) {
  235. if(this.df.options == 'Phone') {
  236. if(v+''=='')return '';
  237. v1 = ''
  238. // phone may start with + and must only have numbers later, '-' and ' ' are stripped
  239. v = v.replace(/ /g, '').replace(/-/g, '').replace(/\(/g, '').replace(/\)/g, '');
  240. // allow initial +,0,00
  241. if(v && v.substr(0,1)=='+') {
  242. v1 = '+'; v = v.substr(1);
  243. }
  244. if(v && v.substr(0,2)=='00') {
  245. v1 += '00'; v = v.substr(2);
  246. }
  247. if(v && v.substr(0,1)=='0') {
  248. v1 += '0'; v = v.substr(1);
  249. }
  250. v1 += cint(v) + '';
  251. callback(v1);
  252. } else if(this.df.options == 'Email') {
  253. if(v+''=='')return '';
  254. if(!validate_email(v)) {
  255. msgprint(wn._("Invalid Email") + ": " + v);
  256. callback("");
  257. } else
  258. callback(v);
  259. } else {
  260. callback(v);
  261. }
  262. }
  263. });
  264. wn.ui.form.ControlPassword = wn.ui.form.ControlData.extend({
  265. input_type: "password"
  266. });
  267. wn.ui.form.ControlInt = wn.ui.form.ControlData.extend({
  268. make_input: function() {
  269. this._super();
  270. this.$input.css({"text-align": "right"})
  271. },
  272. validate: function(value, callback) {
  273. return callback(cint(value, null));
  274. }
  275. });
  276. wn.ui.form.ControlFloat = wn.ui.form.ControlInt.extend({
  277. validate: function(value, callback) {
  278. return callback(isNaN(parseFloat(value)) ? null : flt(value));
  279. },
  280. format_for_input: function(value) {
  281. value = format_number(parseFloat(value),
  282. null, cint(wn.boot.sysdefaults.float_precision, null));
  283. return isNaN(value) ? "" : value;
  284. }
  285. });
  286. wn.ui.form.ControlCurrency = wn.ui.form.ControlFloat.extend({
  287. format_for_input: function(value) {
  288. value = format_number(parseFloat(value),
  289. get_number_format(this.get_currency()));
  290. return isNaN(value) ? "" : value;
  291. },
  292. get_currency: function() {
  293. return wn.meta.get_field_currency(this.df, this.get_doc());
  294. }
  295. });
  296. wn.ui.form.ControlPercent = wn.ui.form.ControlFloat;
  297. wn.ui.form.ControlDate = wn.ui.form.ControlData.extend({
  298. datepicker_options: {
  299. altFormat:'yy-mm-dd',
  300. changeYear: true,
  301. yearRange: "-70Y:+10Y",
  302. },
  303. make_input: function() {
  304. this._super();
  305. this.set_datepicker();
  306. },
  307. set_datepicker: function() {
  308. this.datepicker_options.dateFormat =
  309. (wn.boot.sysdefaults.date_format || 'yy-mm-dd').replace('yyyy','yy')
  310. this.$input.datepicker(this.datepicker_options);
  311. },
  312. parse: function(value) {
  313. return dateutil.user_to_str(value);
  314. },
  315. format_for_input: function(value) {
  316. return dateutil.str_to_user(value);
  317. },
  318. validate: function(value, callback) {
  319. var value = wn.datetime.validate(value);
  320. if(!value) {
  321. msgprint (wn._("Date must be in format") + ": " + (sys_defaults.date_format || "yyyy-mm-dd"));
  322. callback("");
  323. }
  324. return callback(value);
  325. }
  326. })
  327. import_timepicker = function() {
  328. wn.require("lib/js/lib/jquery/jquery.ui.slider.min.js");
  329. wn.require("lib/js/lib/jquery/jquery.ui.sliderAccess.js");
  330. wn.require("lib/js/lib/jquery/jquery.ui.timepicker-addon.css");
  331. wn.require("lib/js/lib/jquery/jquery.ui.timepicker-addon.js");
  332. }
  333. wn.ui.form.ControlTime = wn.ui.form.ControlData.extend({
  334. make_input: function() {
  335. import_timepicker();
  336. this._super();
  337. this.$input.timepicker({
  338. timeFormat: 'hh:mm:ss',
  339. });
  340. }
  341. });
  342. wn.ui.form.ControlDatetime = wn.ui.form.ControlDate.extend({
  343. set_datepicker: function() {
  344. this.datepicker_options.dateFormat =
  345. (wn.boot.sysdefaults.date_format || 'yy-mm-dd').replace('yyyy','yy')
  346. this.datepicker_options.timeFormat = "hh:mm:ss";
  347. this.$input.datetimepicker(this.datepicker_options);
  348. },
  349. make_input: function() {
  350. import_timepicker();
  351. this._super();
  352. },
  353. });
  354. wn.ui.form.ControlText = wn.ui.form.ControlData.extend({
  355. html_element: "textarea"
  356. });
  357. wn.ui.form.ControlLongText = wn.ui.form.ControlText;
  358. wn.ui.form.ControlSmallText = wn.ui.form.ControlText;
  359. wn.ui.form.ControlCheck = wn.ui.form.ControlData.extend({
  360. input_type: "checkbox",
  361. make_wrapper: function() {
  362. this.$wrapper = $('<div class="checkbox">\
  363. <label class="input-area">\
  364. <span class="disp-area" style="display:none;"></span>\
  365. <span class="label-area"></span>\
  366. </label>\
  367. </div>').appendTo(this.parent)
  368. },
  369. set_input_areas: function() {
  370. this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0);
  371. this.input_area = this.$wrapper.find(".input-area").get(0);
  372. this.disp_area = this.$wrapper.find(".disp-area").get(0);
  373. },
  374. make_input: function() {
  375. this._super();
  376. this.$input.removeClass("col-span-12");
  377. },
  378. parse: function(value) {
  379. return this.input.checked ? 1 : 0;
  380. },
  381. validate: function(value, callback) {
  382. return callback(cint(value));
  383. },
  384. set_input: function(value) {
  385. this.input.checked = value ? 1 : 0;
  386. this.last_value = value;
  387. }
  388. });
  389. wn.ui.form.ControlButton = wn.ui.form.ControlData.extend({
  390. make_input: function() {
  391. var me = this;
  392. this.$input = $('<button class="btn">')
  393. .prependTo(me.input_area)
  394. .on("click", function() {
  395. if(me.frm && me.frm.cscript) {
  396. if(me.frm.cscript[me.df.fieldname]) {
  397. me.frm.script_manager.trigger(me.df.fieldname, me.doctype, me.docname);
  398. } else {
  399. me.frm.runscript(me.df.options, me);
  400. }
  401. }
  402. });
  403. this.input = this.$input.get(0);
  404. this.has_input = true;
  405. },
  406. set_label: function() {
  407. this.$input && this.$input.html(this.df.label);
  408. }
  409. });
  410. wn.ui.form.ControlSelect = wn.ui.form.ControlData.extend({
  411. html_element: "select",
  412. make_input: function() {
  413. var me = this;
  414. this._super();
  415. if(this.df.options=="attach_files:") {
  416. this.setup_attachment();
  417. }
  418. this.set_options();
  419. },
  420. set_input: function(value) {
  421. // refresh options first - (new ones??)
  422. this.set_options();
  423. this.$input.val(value);
  424. // not a possible option, repair
  425. if(this.doctype && this.docname) {
  426. // model value is not an option,
  427. // set the default option (displayed)
  428. var model_value = wn.model.get_value(this.doctype, this.docname, this.df.fieldname);
  429. if(this.$input.val() != (model_value || "")) {
  430. this.set_model_value(this.$input.val());
  431. } else {
  432. this.last_value = value;
  433. }
  434. }
  435. },
  436. setup_attachment: function() {
  437. var me = this;
  438. $(this.input).css({"width": "70%"});
  439. $("<button class='btn' title='"+ wn._("Add attachment") + "'\
  440. style='margin-bottom: 9px; \
  441. padding-left: 6px; padding-right: 6px; margin-left: 6px;'>\
  442. <i class='icon-plus'></i></button>")
  443. .click(function() {
  444. me.frm.attachments.new_attachment();
  445. })
  446. .appendTo(this.input_area);
  447. },
  448. set_options: function() {
  449. var options = this.df.options || [];
  450. if(this.df.options=="attach_files:") {
  451. options = this.get_file_attachment_list();
  452. } else if(typeof this.df.options==="string") {
  453. options = this.df.options.split("\n");
  454. }
  455. if(this.in_filter && options[0] != "") {
  456. options = add_lists([''], options);
  457. }
  458. this.$input.empty().add_options(options || []);
  459. },
  460. get_file_attachment_list: function() {
  461. if(!this.frm) return;
  462. var fl = this.frm.doc.file_list;
  463. if(fl) {
  464. this.set_description("");
  465. var fl = JSON.parse(fl),
  466. options = [];
  467. for(var fname in fl) {
  468. if(fname.substr(0,4)!="http")
  469. fname = "files/" + fname;
  470. options.push(fname);
  471. }
  472. return options;
  473. } else {
  474. this.set_description(wn._("Please attach a file first."))
  475. return [""];
  476. }
  477. }
  478. });
  479. // special features for link
  480. // buttons
  481. // autocomplete
  482. // link validation
  483. // custom queries
  484. // add_fetches
  485. wn.ui.form.ControlLink = wn.ui.form.ControlData.extend({
  486. make_input: function() {
  487. $('<div class="input-group link-field">\
  488. <input type="text">\
  489. <div class="input-group-btn">\
  490. <button class="btn btn-search" title="Search Link">\
  491. <i class="icon-search"></i>\
  492. </button>\
  493. <button class="btn btn-open" title="Open Link">\
  494. <i class="icon-play"></i>\
  495. </button><button class="btn btn-new" title="Make New">\
  496. <i class="icon-plus"></i>\
  497. </button>\
  498. </div>\
  499. </div>').appendTo(this.input_area);
  500. this.$input_area = $(this.input_area);
  501. this.$input = this.$input_area.find('input');
  502. this.set_input_attributes();
  503. this.input = this.$input.get(0);
  504. this.has_input = true;
  505. //this.bind_change_event();
  506. var me = this;
  507. this.$input.on("blur", function() {
  508. if(me.doctype && me.docname && !me.autocomplete_open) {
  509. var value = me.get_value();
  510. if(value!==me.last_value) {
  511. me.parse_validate_and_set_in_model(value);
  512. }
  513. }});
  514. this.setup_buttons();
  515. this.setup_autocomplete();
  516. },
  517. setup_buttons: function() {
  518. var me = this;
  519. // magnifier - search
  520. this.$input_area.find(".btn-search").on("click", function() {
  521. new wn.ui.form.LinkSelector({
  522. doctype: me.df.options,
  523. target: me,
  524. txt: me.$input.val()
  525. });
  526. });
  527. // open
  528. if(wn.model.can_read(me.df.options)) {
  529. this.$input_area.find(".btn-open").on("click", function() {
  530. var value = me.get_value();
  531. if(value && me.df.options) wn.set_route("Form", me.df.options, value);
  532. });
  533. } else {
  534. this.$input_area.find(".btn-open").remove();
  535. }
  536. // new
  537. if(wn.model.can_create(me.df.options)) {
  538. this.$input_area.find(".btn-new").on("click", function() {
  539. new_doc(me.df.options)
  540. });
  541. } else {
  542. this.$input_area.find(".btn-new").remove();
  543. }
  544. },
  545. setup_autocomplete: function() {
  546. var me = this;
  547. // increasing zindex of input to increase zindex of autosuggest
  548. // because of the increase in zindex of dialog_wrapper
  549. if(cur_dialog || me.dialog_wrapper) {
  550. var $dialog_wrapper = $(cur_dialog ? cur_dialog.wrapper : me.dialog_wrapper)
  551. var zindex = cint($dialog_wrapper.css("z-index"));
  552. this.$input.css({"z-index": (zindex >= 10 ? zindex : 10) + 1});
  553. }
  554. this.$input.autocomplete({
  555. source: function(request, response) {
  556. var args = {
  557. 'txt': request.term,
  558. 'dt': me.df.options,
  559. };
  560. me.set_custom_query(args);
  561. wn.call({
  562. type: "GET",
  563. method:'webnotes.widgets.search.search_link',
  564. args: args,
  565. callback: function(r) {
  566. response(r.results);
  567. },
  568. });
  569. },
  570. open: function(event, ui) {
  571. me.autocomplete_open = true;
  572. },
  573. close: function(event, ui) {
  574. me.autocomplete_open = false;
  575. },
  576. select: function(event, ui) {
  577. me.set_model_value(ui.item.value);
  578. }
  579. }).data('autocomplete')._renderItem = function(ul, item) {
  580. return $('<li></li>')
  581. .data('item.autocomplete', item)
  582. .append(repl('<a><span style="font-weight: bold;">%(label)s</span><br>\
  583. <span style="font-size:10px;">%(info)s</span></a>',
  584. item))
  585. .appendTo(ul);
  586. };
  587. },
  588. set_custom_query: function(args) {
  589. if(this.get_query) {
  590. var q = this.get_query(this.frm && this.frm.doc, this.doctype, this.docname);
  591. if (typeof(q)==="string") {
  592. args.query = q;
  593. } else if($.isPlainObject(q)) {
  594. if(q.filters) {
  595. $.each(q.filters, function(key, value) {
  596. q.filters[key] = value===undefined ? null : value;
  597. });
  598. }
  599. $.extend(args, q);
  600. }
  601. }
  602. },
  603. validate: function(value, callback) {
  604. // validate the value just entered
  605. var me = this;
  606. if(this.df.options=="[Select]") {
  607. callback(value);
  608. return;
  609. }
  610. var fetch = '';
  611. if(me.frm.fetch_dict[me.df.fieldname])
  612. fetch = me.frm.fetch_dict[me.df.fieldname].columns.join(', ');
  613. wn.call({
  614. method:'webnotes.widgets.form.utils.validate_link',
  615. type: "GET",
  616. args: {
  617. 'value': value,
  618. 'options':me.df.options,
  619. 'fetch': fetch
  620. },
  621. callback: function(r) {
  622. if(r.message=='Ok') {
  623. callback(value);
  624. if(r.fetch_values)
  625. me.set_fetch_values(r.fetch_values);
  626. } else {
  627. callback("")
  628. }
  629. }
  630. });
  631. },
  632. set_fetch_values: function() {
  633. var fl = this.frm.fetch_dict[this.df.fieldname].fields;
  634. for(var i=0; i< fl.length; i++) {
  635. wn.model.set_value(this.doctype, this.docname. fl[i], fetch_values[i]);
  636. }
  637. }
  638. });
  639. wn.ui.form.ControlCode = wn.ui.form.ControlInput.extend({
  640. editor_name: "ACE",
  641. make_input: function() {
  642. $(this.input_area).css({"min-height":"360px"});
  643. var me = this;
  644. this.editor = new wn.editors[this.editor_name]({
  645. parent: this.input_area,
  646. change: function(value) {
  647. me.parse_validate_and_set_in_model(value);
  648. },
  649. field: this
  650. });
  651. this.has_input = true;
  652. },
  653. get_value: function() {
  654. this.editor.get_value();
  655. },
  656. set_input: function(value) {
  657. this.editor.set_input(value);
  658. this.last_value = value;
  659. }
  660. });
  661. wn.ui.form.ControlTextEditor = wn.ui.form.ControlCode.extend({
  662. editor_name: "BootstrapWYSIWYG"
  663. });
  664. wn.ui.form.ControlTable = wn.ui.form.Control.extend({
  665. make: function() {
  666. this._super();
  667. this.grid = new wn.ui.form.Grid({
  668. frm: this.frm,
  669. df: this.df,
  670. perm: this.perm,
  671. parent: this.wrapper
  672. })
  673. if(this.frm)
  674. this.frm.grids[this.frm.grids.length] = this;
  675. // description
  676. if(this.df.description) {
  677. $('<p class="text-muted small">' + this.df.description + '</p>')
  678. .appendTo(this.wrapper);
  679. }
  680. var me = this;
  681. this.$wrapper.on("refresh", function() {
  682. me.grid.refresh();
  683. return false;
  684. });
  685. }
  686. })