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.
 
 
 
 
 
 

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