Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

690 rader
18 KiB

  1. // Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. //
  3. // MIT License (MIT)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a
  6. // copy of this software and associated documentation files (the "Software"),
  7. // to deal in the Software without restriction, including without limitation
  8. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. // and/or sell copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. //
  22. // fields.js
  23. //
  24. // Fields are divided into 2 types
  25. // 1. Standard fields are loaded with the libarary
  26. // 2. Special fields are loaded with form.compressed.js
  27. //
  28. //
  29. // + wrapper
  30. // + input_area
  31. // + display_area
  32. // ======================================================================================
  33. var no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'FlexTable', 'Button', 'Image'];
  34. var codeid=0; var code_editors={};
  35. function Field() {
  36. this.with_label = 1;
  37. }
  38. Field.prototype.make_body = function() {
  39. // parent element
  40. this.$wrapper = $('<div class="control-group" style="max-width: 600px;">\
  41. <label class="control-label"></label>\
  42. <div class="controls">\
  43. <div class="control-input"></div>\
  44. <div class="control-value"></div>\
  45. </div>\
  46. </div>').appendTo(this.parent);
  47. this.wrapper = this.$wrapper.get(0);
  48. this.label_area = this.label_span = this.$wrapper.find(".control-label").get(0);
  49. this.input_area = this.$wrapper.find(".control-input").get(0);
  50. this.disp_area = this.$wrapper.find(".control-value").get(0);
  51. // set description
  52. this.set_description();
  53. if(this.onmake)this.onmake();
  54. }
  55. Field.prototype.set_max_width = function() {
  56. var no_max = ['Code', 'Text Editor', 'Text', 'Small Text', 'Table', 'HTML']
  57. if(this.wrapper && this.layout_cell && this.layout_cell.parentNode.cells
  58. && this.layout_cell.parentNode.cells.length==1 && !in_list(no_max, this.df.fieldtype)) {
  59. $y(this.wrapper, {paddingRight:'50%'});
  60. }
  61. }
  62. Field.prototype.set_label = function(label) {
  63. this.label_span.innerHTML = wn._(label || this.df.label);
  64. }
  65. Field.prototype.set_description = function(txt) {
  66. if(txt) {
  67. if(!this.$wrapper.find(".help-box").length) {
  68. $('<p class="help-box small"></p>').appendTo(this.input_area);
  69. }
  70. this.$wrapper.find(".help-box").html(txt);
  71. } else {
  72. this.$wrapper.find(".help-box").empty().toggle(false);
  73. }
  74. }
  75. Field.prototype.get_status = function(explain) {
  76. if(!this.doctype)
  77. return "Write";
  78. return wn.perm.get_field_display_status(this.df,
  79. locals[this.doctype][this.docname], this.perm, explain)
  80. }
  81. Field.prototype.refresh_mandatory = function() {
  82. if(this.in_filter)return;
  83. //this.$wrapper.toggleClass("has-warning", cint(this.df.reqd) ? true : false);
  84. this.refresh_label_icon()
  85. }
  86. Field.prototype.refresh_display = function() {
  87. // from permission
  88. if(!this.current_status || this.current_status!=this.disp_status) { // status changed
  89. if(this.disp_status=='Write') { // write
  90. if(this.make_input&&(!this.input)) { // make input if reqd
  91. this.make_input();
  92. if(this.txt || this.input)
  93. $(this.txt || this.input).addClass("mousetrap");
  94. if(this.onmake_input) this.onmake_input();
  95. }
  96. if(this.show) this.show()
  97. else { $ds(this.wrapper); }
  98. // input or content
  99. if(this.input) { // if there, show it!
  100. $ds(this.input_area);
  101. $dh(this.disp_area);
  102. if(this.input.refresh)
  103. this.input.refresh();
  104. } else { // no widget
  105. $dh(this.input_area);
  106. $ds(this.disp_area);
  107. }
  108. } else if(this.disp_status=='Read') {
  109. // read
  110. if(this.show) this.show()
  111. else { $ds(this.wrapper); }
  112. $dh(this.input_area);
  113. $ds(this.disp_area);
  114. } else {
  115. // None - hide all
  116. if(this.hide) this.hide();
  117. else $dh(this.wrapper);
  118. }
  119. this.current_status = this.disp_status;
  120. }
  121. }
  122. Field.prototype.refresh = function() {
  123. // get status
  124. this.disp_status = this.get_status();
  125. // if there is a special refresh in case of table, then this is not valid
  126. if(this.in_grid
  127. && this.table_refresh
  128. && this.disp_status == 'Write')
  129. { this.table_refresh(); return; }
  130. this.set_label();
  131. this.refresh_display();
  132. if(this.input) {
  133. if(this.input.refresh) this.input.refresh(this.df);
  134. }
  135. // further refresh
  136. if(this.onrefresh)
  137. this.onrefresh(); // called by various fields
  138. if(this.wrapper) {
  139. this.wrapper.fieldobj = this;
  140. $(this.wrapper).trigger('refresh');
  141. }
  142. if(!this.not_in_form)
  143. this.set_input(_f.get_value(this.doctype,this.docname,this.df.fieldname));
  144. this.refresh_mandatory();
  145. this.set_max_width();
  146. }
  147. Field.prototype.refresh_label_icon = function() {
  148. // mandatory
  149. var to_update = false;
  150. if(this.df.reqd && this.get_value && is_null(this.get_value()))
  151. to_update = true;
  152. this.$wrapper.toggleClass("has-error", to_update);
  153. }
  154. Field.prototype.set = function(val) {
  155. // not in form
  156. if(this.not_in_form)
  157. return;
  158. if((!this.docname) && this.grid) {
  159. this.docname = this.grid.add_newrow(); // new row
  160. }
  161. if(this.validate)
  162. val = this.validate(val);
  163. cur_frm.set_value_in_locals(this.doctype, this.docname,
  164. this.df.fieldname, val);
  165. this.value = val; // for return
  166. }
  167. Field.prototype.set_input = function(val) {
  168. this.value = val;
  169. if(this.input && this.input.set_input) {
  170. this.input.set_input(val); // in widget
  171. }
  172. var disp_val = val;
  173. if(val==null)
  174. disp_val = '';
  175. this.set_disp(disp_val); // text
  176. }
  177. Field.prototype.run_trigger = function() {
  178. // update mandatory icon
  179. this.refresh_label_icon();
  180. if(this.not_in_form) {
  181. return;
  182. }
  183. if(cur_frm.cscript[this.df.fieldname])
  184. cur_frm.runclientscript(this.df.fieldname, this.doctype, this.docname);
  185. cur_frm.refresh_dependency();
  186. }
  187. Field.prototype.set_disp_html = function(t) {
  188. if(this.disp_area){
  189. $(this.disp_area).addClass('disp-area');
  190. this.disp_area.innerHTML = (t==null ? '' : t);
  191. if(!t) $(this.disp_area).addClass('disp-area-no-val');
  192. }
  193. }
  194. Field.prototype.set_disp = function(val) {
  195. this.set_disp_html(val);
  196. }
  197. Field.prototype.get_input = function() {
  198. return this.txt || this.input;
  199. }
  200. function DataField() { } DataField.prototype = new Field();
  201. DataField.prototype.make_input = function() {
  202. var me = this;
  203. this.input = $a_input(this.input_area, this.df.fieldtype=='Password' ? 'password' : 'text');
  204. if(this.df.placeholder) $(this.input).attr("placeholder", this.df.placeholder);
  205. this.get_value= function() {
  206. var v = this.input.value;
  207. if(this.validate)
  208. v = this.validate(v);
  209. return v;
  210. }
  211. this.input.name = this.df.fieldname;
  212. $(this.input).blur(function() {
  213. me.set_value(me.get_value ? me.get_value() : $(this).val());
  214. });
  215. this.set_value = function(val) {
  216. if(!me.last_value) me.last_value=undefined;
  217. if(me.validate) {
  218. val = me.validate(val);
  219. if(me.last_value === val) return;
  220. me.input.value = (val==undefined) ? '' : val;
  221. } else if(me.last_value === val) { return; }
  222. me.set(val);
  223. if(me.format_input)
  224. me.format_input();
  225. if(in_list(['Currency','Float','Int'], me.df.fieldtype)) {
  226. if(flt(me.last_value)==flt(val)) {
  227. me.last_value = val;
  228. return; // do not run trigger
  229. }
  230. }
  231. me.last_value = val;
  232. me.run_trigger();
  233. }
  234. this.input.set_input = function(val) {
  235. if(val==null)val='';
  236. me.input.value = val;
  237. if(me.format_input)me.format_input();
  238. }
  239. }
  240. DataField.prototype.validate = function(v) {
  241. if(this.df.options == 'Phone') {
  242. if(v+''=='')return '';
  243. v1 = ''
  244. // phone may start with + and must only have numbers later, '-' and ' ' are stripped
  245. v = v.replace(/ /g, '').replace(/-/g, '').replace(/\(/g, '').replace(/\)/g, '');
  246. // allow initial +,0,00
  247. if(v && v.substr(0,1)=='+') {
  248. v1 = '+'; v = v.substr(1);
  249. }
  250. if(v && v.substr(0,2)=='00') {
  251. v1 += '00'; v = v.substr(2);
  252. }
  253. if(v && v.substr(0,1)=='0') {
  254. v1 += '0'; v = v.substr(1);
  255. }
  256. v1 += cint(v) + '';
  257. return v1;
  258. } else if(this.df.options == 'Email') {
  259. if(v+''=='')return '';
  260. if(!validate_email(v)) {
  261. msgprint(this.df.label + ': ' + v + ' is not a valid email id');
  262. return '';
  263. } else
  264. return v;
  265. } else {
  266. return v;
  267. }
  268. }
  269. // ======================================================================================
  270. function HTMLField() {
  271. var me = this;
  272. this.make_body = function() {
  273. me.wrapper = $("<div>").appendTo(me.parent);
  274. }
  275. this.set_disp = function(val) {
  276. if(val)
  277. $(me.wrapper).html(val);
  278. }
  279. this.set_input = function(val) {
  280. me.set_disp(val);
  281. }
  282. this.refresh = function() {
  283. if(me.df.options)
  284. me.set_disp(me.df.options);
  285. }
  286. }
  287. // reference when a new record is created via link
  288. function LinkField() { } LinkField.prototype = new Field();
  289. LinkField.prototype.make_input = function() {
  290. var me = this;
  291. if(me.df.no_buttons) {
  292. this.txt = $("<input type='text'>")
  293. .appendTo(this.input_area).get(0);
  294. this.input = this.txt;
  295. } else {
  296. me.input = me.input_area;
  297. me.input_group = $('<div class="input-group link-field">').appendTo(me.input_area);
  298. me.txt = $('<input type="text" style="margin-right: 0px;">')
  299. .appendTo(me.input_group).get(0);
  300. me.input_group_btn = $('<div class="input-group-btn">').appendTo(me.input_group);
  301. me.btn = $('<button class="btn" title="'+wn._('Search Link')+'">\
  302. <i class="icon-search"></i></button>').appendTo(me.input_group_btn).get(0);
  303. me.btn1 = $('<button class="btn" title="'+wn._('Open Link')+'">\
  304. <i class="icon-play"></i></button>').appendTo(me.input_group_btn).get(0);
  305. me.btn2 = $('<button class="btn" title="'+wn._('Make New')+'">\
  306. <i class="icon-plus"></i></button>').appendTo(me.input_group_btn).get(0);
  307. me.txt.name = me.df.fieldname;
  308. me.setdisabled = function(tf) { me.txt.disabled = tf; }
  309. // setup buttons
  310. me.setup_buttons();
  311. }
  312. me.onrefresh = function() {
  313. var can_create = in_list(wn.boot.profile.can_create, me.df.options);
  314. var can_read = in_list(wn.boot.profile.can_read, me.df.options);
  315. if(!can_create) $(this.btn2).remove();
  316. if(!can_read) $(this.btn1).remove();
  317. }
  318. me.onrefresh();
  319. me.txt.field_object = this;
  320. // set onchange triggers
  321. me.input.set_input = function(val) {
  322. if(val==undefined)val='';
  323. me.txt.value = val;
  324. }
  325. me.get_value = function() { return me.txt.value; }
  326. // increasing zindex of input to increase zindex of autosuggest
  327. // because of the increase in zindex of dialog_wrapper
  328. if(cur_dialog || me.dialog_wrapper) {
  329. var $dialog_wrapper = $(cur_dialog ? cur_dialog.wrapper : me.dialog_wrapper)
  330. var zindex = cint($dialog_wrapper.css("z-index"));
  331. $(me.txt).css({"z-index": (zindex >= 10 ? zindex : 10) + 1});
  332. }
  333. $(me.txt).autocomplete({
  334. source: function(request, response) {
  335. var args = {
  336. 'txt': request.term,
  337. 'dt': me.df.options,
  338. };
  339. var q = me.get_custom_query();
  340. if (typeof(q)==="string") {
  341. args.query = q;
  342. } else if($.isPlainObject(q)) {
  343. if(q.filters) {
  344. $.each(q.filters, function(key, value) {
  345. q.filters[key] = value===undefined ? null : value;
  346. });
  347. }
  348. $.extend(args, q);
  349. }
  350. wn.call({
  351. method:'webnotes.widgets.search.search_link',
  352. args: args,
  353. callback: function(r) {
  354. response(r.results);
  355. },
  356. });
  357. },
  358. select: function(event, ui) {
  359. me.set_input_value(ui.item.value);
  360. }
  361. }).data('autocomplete')._renderItem = function(ul, item) {
  362. return $('<li></li>')
  363. .data('item.autocomplete', item)
  364. .append(repl('<a><span style="font-weight: bold;">%(label)s</span><br>\
  365. <span style="font-size:10px;">%(info)s</span></a>',
  366. item))
  367. .appendTo(ul);
  368. };
  369. $(this.txt).change(function() {
  370. var val = $(this).val();//me.get_value();
  371. me.set_input_value_executed = false;
  372. if(!val) {
  373. if(selector && selector.display)
  374. return;
  375. me.set_input_value('');
  376. } else {
  377. // SetTimeout hack! if in put is set via autocomplete, do not validate twice
  378. setTimeout(function() {
  379. if (!me.set_input_value_executed) {
  380. me.set_input_value(val);
  381. }
  382. }, 1000);
  383. }
  384. })
  385. }
  386. LinkField.prototype.get_custom_query = function() {
  387. this.set_get_query();
  388. if(this.get_query) {
  389. if(cur_frm)
  390. var doc = locals[cur_frm.doctype][cur_frm.docname];
  391. return this.get_query(doc, this.doctype, this.docname);
  392. }
  393. }
  394. LinkField.prototype.setup_buttons = function() {
  395. var me = this;
  396. // magnifier - search
  397. me.btn.onclick = function() {
  398. selector.set(me, me.df.options, me.df.label);
  399. selector.show(me.txt);
  400. }
  401. // open
  402. if(me.btn1)me.btn1.onclick = function() {
  403. if(me.txt.value && me.df.options) { loaddoc(me.df.options, me.txt.value); }
  404. }
  405. // add button - for inline creation of records
  406. me.can_create = 0;
  407. if((!me.not_in_form) && in_list(profile.can_create, me.df.options)) {
  408. me.can_create = 1;
  409. me.btn2.onclick = function() {
  410. var on_save_callback = function(new_rec) {
  411. if(new_rec) {
  412. var d = _f.calling_doc_stack.pop(); // patch for composites
  413. locals[d[0]][d[1]][me.df.fieldname] = new_rec;
  414. me.refresh();
  415. if(me.grid)me.grid.refresh();
  416. // call script
  417. me.run_trigger();
  418. }
  419. }
  420. _f.calling_doc_stack.push([me.doctype, me.docname]);
  421. new_doc(me.df.options);
  422. }
  423. } else {
  424. $(me.btn2).remove();
  425. }
  426. }
  427. LinkField.prototype.set_input_value = function(val) {
  428. var me = this;
  429. // SetTimeout hack! if in put is set via autocomplete, do not validate twice
  430. me.set_input_value_executed = true;
  431. var from_selector = false;
  432. if(selector && selector.display) from_selector = true;
  433. // refresh mandatory style
  434. me.refresh_label_icon();
  435. // not in form, do nothing
  436. if(me.not_in_form) {
  437. $(this.txt).val(val);
  438. return;
  439. }
  440. // same value, do nothing
  441. if(cur_frm) {
  442. if(val == locals[me.doctype][me.docname][me.df.fieldname]) {
  443. //me.set(val); // one more time, grid bug?
  444. me.run_trigger(); // wanted - called as refresh?
  445. return;
  446. }
  447. }
  448. // set in locals
  449. me.set(val);
  450. // deselect cell if in grid
  451. if(_f.cur_grid_cell)
  452. _f.cur_grid_cell.grid.cell_deselect();
  453. if(val) {
  454. // validate only if val is not empty
  455. me.validate_link(val, from_selector);
  456. } else {
  457. // run trigger if value is cleared
  458. me.run_trigger();
  459. }
  460. }
  461. LinkField.prototype.validate_link = function(val, from_selector) {
  462. // validate the value just entered
  463. var me = this;
  464. if(this.df.options=="[Select]") {
  465. $(me.txt).val(val);
  466. me.run_trigger();
  467. return;
  468. }
  469. var fetch = '';
  470. if(cur_frm.fetch_dict[me.df.fieldname])
  471. fetch = cur_frm.fetch_dict[me.df.fieldname].columns.join(', ');
  472. $c('webnotes.widgets.form.utils.validate_link', {
  473. 'value':val,
  474. 'options':me.df.options,
  475. 'fetch': fetch
  476. },
  477. function(r,rt) {
  478. if(r.message=='Ok') {
  479. // set fetch values
  480. if($(me.txt).val()!=val) {
  481. if((me.grid && !from_selector) || (!me.grid)) {
  482. $(me.txt).val(val);
  483. }
  484. }
  485. if(r.fetch_values)
  486. me.set_fetch_values(r.fetch_values);
  487. me.run_trigger();
  488. } else {
  489. me.txt.value = '';
  490. me.set('');
  491. }
  492. }
  493. );
  494. }
  495. LinkField.prototype.set_fetch_values = function(fetch_values) {
  496. var fl = cur_frm.fetch_dict[this.df.fieldname].fields;
  497. var changed_fields = [];
  498. for(var i=0; i< fl.length; i++) {
  499. if(locals[this.doctype][this.docname][fl[i]]!=fetch_values[i]) {
  500. locals[this.doctype][this.docname][fl[i]] = fetch_values[i];
  501. if(!this.grid) {
  502. refresh_field(fl[i]);
  503. // call trigger on the target field
  504. changed_fields.push(fl[i]);
  505. }
  506. }
  507. }
  508. // run triggers
  509. for(i=0; i<changed_fields.length; i++) {
  510. if(cur_frm.fields_dict[changed_fields[i]]) // on main
  511. cur_frm.fields_dict[changed_fields[i]].run_trigger();
  512. }
  513. // refresh grid
  514. if(this.grid) this.grid.refresh();
  515. }
  516. LinkField.prototype.set_get_query = function() {
  517. if(this.get_query)return;
  518. if(this.grid) {
  519. var f = this.grid.get_field(this.df.fieldname);
  520. if(f.get_query) this.get_query = f.get_query;
  521. }
  522. }
  523. LinkField.prototype.set_disp = function(val) {
  524. var t = null;
  525. if(val)t = "<a href=\'javascript:loaddoc(\""+this.df.options+"\", \""+val+"\")\'>"+val+"</a>";
  526. this.set_disp_html(t);
  527. }
  528. var tmpid = 0;
  529. function make_field(docfield, doctype, parent, frm, in_grid, hide_label) { // Factory
  530. if(["Data", "Int", "Float", "Currency", "HTML", "Date", "Time", "DateTime",
  531. "Text", "Small Text", "Long Text", "Check", "Button", "Select",
  532. "Password", "Read Only"].indexOf(docfield.fieldtype)!=-1) {
  533. return new wn.ui.form.make_control({
  534. df: docfield,
  535. doctype: doctype,
  536. parent: parent,
  537. hide_label: hide_label,
  538. frm: frm
  539. });
  540. }
  541. switch(docfield.fieldtype.toLowerCase()) {
  542. // general fields
  543. case 'percent':var f = new PercentField(); break;
  544. case 'link':var f = new LinkField(); break;
  545. // form fields
  546. case 'code':var f = new _f.CodeField(); break;
  547. case 'text editor':var f = new _f.CodeField(); break;
  548. case 'table':var f = new _f.TableField(); break;
  549. case 'image':var f= new _f.ImageField(); break;
  550. }
  551. if(!f) console.log(docfield.fieldtype)
  552. f.parent = parent;
  553. f.doctype = doctype;
  554. f.df = docfield;
  555. f.perm = frm ? frm.perm : [[1,1,1]];
  556. if(_f)
  557. f.col_break_width = _f.cur_col_break_width;
  558. if(in_grid) {
  559. f.in_grid = true;
  560. f.with_label = 0;
  561. }
  562. if(hide_label) {
  563. f.with_label = 0;
  564. }
  565. if(frm) {
  566. f.frm = frm;
  567. if(parent)
  568. f.layout_cell = parent.parentNode;
  569. }
  570. if(f.init) f.init();
  571. f.make_body();
  572. return f;
  573. }