You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1351 lines
36 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. var ischk = (this.df.fieldtype=='Check' ? 1 : 0);
  40. // parent element
  41. if(this.parent)
  42. this.wrapper = $a(this.parent, (this.with_label ? 'div' : 'span'));
  43. else
  44. this.wrapper = document.createElement((this.with_label ? 'div' : 'span'));
  45. this.label_area = $a(this.wrapper, 'div', '', {margin:'8px 0px 2px 0px'});
  46. if(ischk && !this.in_grid) {
  47. this.input_area = $a(this.label_area, 'span', '', {marginRight:'4px'});
  48. this.disp_area = $a(this.label_area, 'span', '', {marginRight:'4px'});
  49. }
  50. // label
  51. if(this.with_label) {
  52. this.label_span = $a(this.label_area, 'span', 'field_label')
  53. // error icon
  54. this.label_icon = $a(this.label_area,'img','',{margin:'-3px 4px -3px 4px'}); $dh(this.label_icon);
  55. this.label_icon.src = 'lib/images/icons/error.gif';
  56. this.label_icon.title = 'Mandatory value needs to be entered';
  57. // error icon
  58. this.suggest_icon = $a(this.label_area,'img','',{margin:'-3px 4px -3px 0px'}); $dh(this.suggest_icon);
  59. this.suggest_icon.src = 'lib/images/icons/bullet_arrow_down.png';
  60. this.suggest_icon.title = 'With suggestions';
  61. } else {
  62. this.label_span = $a(this.label_area, 'span', '', {marginRight:'4px'})
  63. $dh(this.label_area);
  64. }
  65. // make the input areas
  66. if(!this.input_area) {
  67. this.input_area = $a(this.wrapper, (this.with_label ? 'div' : 'span'));
  68. this.disp_area = $a(this.wrapper, (this.with_label ? 'div' : 'span'));
  69. }
  70. // apply style
  71. if(this.in_grid) {
  72. if(this.label_area) $dh(this.label_area);
  73. } else {
  74. this.input_area.className = 'input_area';
  75. $y(this.wrapper,{marginBottom:'4px'});
  76. // set description
  77. this.set_description();
  78. }
  79. // bind label refresh
  80. if(this.onmake)this.onmake();
  81. }
  82. Field.prototype.set_max_width = function() {
  83. var no_max = ['Code', 'Text Editor', 'Text', 'Table', 'HTML']
  84. if(this.wrapper && this.layout_cell && this.layout_cell.parentNode.cells
  85. && this.layout_cell.parentNode.cells.length==1 && !in_list(no_max, this.df.fieldtype)) {
  86. $y(this.wrapper, {paddingRight:'50%'});
  87. }
  88. }
  89. Field.prototype.set_label = function() {
  90. if(this.with_label && this.label_area && this.label!=this.df.label) {
  91. this.label_span.innerHTML = this.df.label;this.label = this.df.label;
  92. }
  93. }
  94. Field.prototype.set_description = function() {
  95. if(this.df.description) {
  96. // parent
  97. var p = in_list(['Text Editor', 'Code', 'Check'], this.df.fieldtype) ? this.label_area : this.wrapper;
  98. this.desc_area = $a(p, 'div', 'field_description', '', this.df.description)
  99. // padding on the bottom
  100. if(in_list(['Text Editor', 'Code'], this.df.fieldtype))
  101. $(this.desc_area).addClass('field_description_top');
  102. }
  103. }
  104. // Field Refresh
  105. // --------------------------------------------------------------------------------------------
  106. Field.prototype.get_status = function() {
  107. // if used in filters
  108. if(this.in_filter) this.not_in_form = this.in_filter;
  109. if(this.not_in_form) {
  110. return 'Write';
  111. }
  112. var fn = this.df.fieldname?this.df.fieldname:this.df.label;
  113. this.df = get_field(this.doctype, fn, this.docname);
  114. if(!this.df.permlevel) this.df.permlevel = 0;
  115. var p = this.perm[this.df.permlevel];
  116. var ret;
  117. // permission level
  118. if(cur_frm.editable && p && p[WRITE])ret='Write';
  119. else if(p && p[READ])ret='Read';
  120. else ret='None';
  121. // binary
  122. if(this.df.fieldtype=='Binary')
  123. ret = 'None'; // no display for binary
  124. // hidden
  125. if(cint(this.df.hidden))
  126. ret = 'None';
  127. // for submit
  128. if(ret=='Write' && cint(cur_frm.doc.docstatus) > 0) ret = 'Read';
  129. // allow on submit
  130. var a_o_s = cint(this.df.allow_on_submit);
  131. if(a_o_s && (this.in_grid || (this.frm && this.frm.not_in_container))) {
  132. a_o_s = null;
  133. if(this.in_grid) a_o_s = this.grid.field.df.allow_on_submit; // take from grid
  134. if(this.frm && this.frm.not_in_container) { a_o_s = cur_grid.field.df.allow_on_submit;} // take from grid
  135. }
  136. if(cur_frm.editable && a_o_s && cint(cur_frm.doc.docstatus)>0 && !this.df.hidden) {
  137. tmp_perm = get_perm(cur_frm.doctype, cur_frm.docname, 1);
  138. if(tmp_perm[this.df.permlevel] && tmp_perm[this.df.permlevel][WRITE])ret='Write';
  139. }
  140. return ret;
  141. }
  142. Field.prototype.set_style_mandatory = function(add) {
  143. if(add) {
  144. $(this.txt ? this.txt : this.input).addClass('input-mandatory');
  145. if(this.disp_area) $(this.disp_area).addClass('input-mandatory');
  146. } else {
  147. $(this.txt ? this.txt : this.input).removeClass('input-mandatory');
  148. if(this.disp_area) $(this.disp_area).removeClass('input-mandatory');
  149. }
  150. }
  151. Field.prototype.refresh_mandatory = function() {
  152. if(this.in_filter)return;
  153. // mandatory changes
  154. if(this.df.reqd) {
  155. if(this.label_area) this.label_area.style.color= "#d22";
  156. this.set_style_mandatory(1);
  157. } else {
  158. if(this.label_area) this.label_area.style.color= "#222";
  159. this.set_style_mandatory(0);
  160. }
  161. this.refresh_label_icon()
  162. this.set_reqd = this.df.reqd;
  163. }
  164. Field.prototype.refresh_display = function() {
  165. // from permission
  166. if(!this.current_status || this.current_status!=this.disp_status) { // status changed
  167. if(this.disp_status=='Write') { // write
  168. if(this.make_input&&(!this.input)) { // make input if reqd
  169. this.make_input();
  170. if(this.onmake_input) this.onmake_input();
  171. }
  172. if(this.show) this.show()
  173. else { $ds(this.wrapper); }
  174. // input or content
  175. if(this.input) { // if there, show it!
  176. $ds(this.input_area);
  177. $dh(this.disp_area);
  178. if(this.input.refresh)this.input.refresh();
  179. } else { // no widget
  180. $dh(this.input_area);
  181. $ds(this.disp_area);
  182. }
  183. } else if(this.disp_status=='Read') {
  184. // read
  185. if(this.show) this.show()
  186. else { $ds(this.wrapper); }
  187. $dh(this.input_area);
  188. $ds(this.disp_area);
  189. } else {
  190. // None - hide all
  191. if(this.hide) this.hide();
  192. else $dh(this.wrapper);
  193. }
  194. this.current_status = this.disp_status;
  195. }
  196. }
  197. Field.prototype.refresh = function() {
  198. // get status
  199. this.disp_status = this.get_status();
  200. // if there is a special refresh in case of table, then this is not valid
  201. if(this.in_grid
  202. && this.table_refresh
  203. && this.disp_status == 'Write')
  204. { this.table_refresh(); return; }
  205. this.set_label();
  206. this.refresh_display();
  207. // further refresh
  208. if(this.onrefresh)
  209. this.onrefresh(); // called by various fields
  210. if(this.input) {
  211. if(this.input.refresh) this.input.refresh(this.df);
  212. }
  213. if(this.wrapper) {
  214. this.wrapper.fieldobj = this;
  215. $(this.wrapper).trigger('refresh');
  216. }
  217. if(!this.not_in_form)
  218. this.set_input(_f.get_value(this.doctype,this.docname,this.df.fieldname));
  219. this.refresh_mandatory();
  220. this.set_max_width();
  221. }
  222. Field.prototype.refresh_label_icon = function() {
  223. // mandatory
  224. if(this.df.reqd) {
  225. if(this.get_value && is_null(this.get_value())) {
  226. if(this.label_icon) $ds(this.label_icon);
  227. $(this.txt ? this.txt : this.input).addClass('field-to-update')
  228. } else {
  229. if(this.label_icon) $dh(this.label_icon);
  230. $(this.txt ? this.txt : this.input).removeClass('field-to-update')
  231. }
  232. }
  233. }
  234. // Set / display values
  235. // --------------------------------------------------------------------------------------------
  236. Field.prototype.set = function(val) {
  237. // not in form
  238. if(this.not_in_form)
  239. return;
  240. if((!this.docname) && this.grid) {
  241. this.docname = this.grid.add_newrow(); // new row
  242. }
  243. // cleanup ms word quotes
  244. if(in_list(['Data','Text','Small Text','Code'], this.df.fieldtype))
  245. val = clean_smart_quotes(val);
  246. var set_val = val;
  247. if(this.validate)set_val = this.validate(val);
  248. _f.set_value(this.doctype, this.docname, this.df.fieldname, set_val);
  249. this.value = val; // for return
  250. }
  251. Field.prototype.set_input = function(val) {
  252. this.value = val;
  253. if(this.input&&this.input.set_input) {
  254. if(val==null)this.input.set_input('');
  255. else this.input.set_input(val); // in widget
  256. }
  257. var disp_val = val;
  258. if(val==null) disp_val = '';
  259. this.set_disp(disp_val); // text
  260. }
  261. Field.prototype.run_trigger = function() {
  262. // update mandatory icon
  263. this.refresh_label_icon();
  264. if(this.df.reqd && this.get_value && !is_null(this.get_value()) && this.set_as_error)
  265. this.set_as_error(0);
  266. if(this.not_in_form) {
  267. return;
  268. }
  269. if(cur_frm.cscript[this.df.fieldname])
  270. cur_frm.runclientscript(this.df.fieldname, this.doctype, this.docname);
  271. cur_frm.refresh_dependency();
  272. }
  273. Field.prototype.set_disp_html = function(t) {
  274. if(this.disp_area){
  275. $(this.disp_area).addClass('disp_area');
  276. this.disp_area.innerHTML = (t==null ? '' : t);
  277. if(!t) $(this.disp_area).addClass('disp_area_no_val');
  278. }
  279. }
  280. Field.prototype.set_disp = function(val) {
  281. this.set_disp_html(val);
  282. }
  283. Field.prototype.set_as_error = function(set) {
  284. if(this.in_grid || this.in_filter) return;
  285. var w = this.txt ? this.txt : this.input;
  286. if(set) {
  287. $y(w, {border: '2px solid RED'});
  288. } else {
  289. $y(w, {border: '1px solid #888'});
  290. }
  291. }
  292. // Show in GRID
  293. // --------------------------------------------------------------------------------------------
  294. // for grids (activate against a particular record in the table
  295. Field.prototype.activate = function(docname) {
  296. this.docname = docname;
  297. this.refresh();
  298. if(this.input) {
  299. var v = _f.get_value(this.doctype, this.docname, this.df.fieldname);
  300. this.last_value=v;
  301. // set input value
  302. if(this.input.onchange && this.input.get_value && this.input.get_value() !=v) {
  303. if(this.validate)
  304. this.input.set_value(this.validate(v));
  305. else
  306. this.input.set_value((v==null)?'':v);
  307. if(this.format_input)
  308. this.format_input();
  309. }
  310. if(this.input.focus){
  311. try{this.input.focus();} catch(e){} // IE Fix - Unexpected call???
  312. }
  313. }
  314. if(this.txt) {
  315. try{this.txt.focus();} catch(e){} // IE Fix - Unexpected call???
  316. this.txt.field_object = this;
  317. }
  318. }
  319. // ======================================================================================
  320. function DataField() { } DataField.prototype = new Field();
  321. DataField.prototype.make_input = function() {
  322. var me = this;
  323. this.input = $a_input(this.input_area, this.df.fieldtype=='Password' ? 'password' : 'text');
  324. this.get_value= function() {
  325. var v = this.input.value;
  326. if(this.validate)v = this.validate(v);
  327. return v;
  328. }
  329. this.input.name = this.df.fieldname;
  330. $(this.input).change(function() {
  331. me.set_value($(this).val());
  332. });
  333. this.set_value = function(val) {
  334. if(!me.last_value)me.last_value='';
  335. if(me.validate) {
  336. val = me.validate(val);
  337. me.input.value = val;
  338. }
  339. me.set(val);
  340. if(me.format_input)
  341. me.format_input();
  342. if(in_list(['Currency','Float','Int'], me.df.fieldtype)) {
  343. if(flt(me.last_value)==flt(val)) {
  344. me.last_value = val;
  345. return; // do not run trigger
  346. }
  347. }
  348. me.last_value = val;
  349. me.run_trigger();
  350. }
  351. this.input.set_input = function(val) {
  352. if(val==null)val='';
  353. me.input.value = val;
  354. if(me.format_input)me.format_input();
  355. }
  356. // autosuggest type fields
  357. // -----------------------
  358. if(this.df.options=='Suggest') {
  359. // add auto suggest
  360. if(this.suggest_icon) $di(this.suggest_icon);
  361. $(me.input).autocomplete({
  362. source: function(request, response) {
  363. wn.call({
  364. method:'webnotes.widgets.search.search_link',
  365. args: {
  366. 'txt': request.term,
  367. 'dt': me.df.options,
  368. 'query': repl('SELECT DISTINCT `%(fieldname)s` FROM \
  369. `tab%(dt)s` WHERE `%(fieldname)s` LIKE "%s" LIMIT 50',
  370. {fieldname:me.df.fieldname, dt:me.df.parent})
  371. },
  372. callback: function(r) {
  373. response(r.results);
  374. }
  375. });
  376. },
  377. select: function(event, ui) {
  378. me.set_input_value(ui.item.value);
  379. return false;
  380. }
  381. });
  382. }
  383. }
  384. DataField.prototype.validate = function(v) {
  385. if(this.df.options == 'Phone') {
  386. if(v+''=='')return '';
  387. v1 = ''
  388. // phone may start with + and must only have numbers later, '-' and ' ' are stripped
  389. v = v.replace(/ /g, '').replace(/-/g, '').replace(/\(/g, '').replace(/\)/g, '');
  390. // allow initial +,0,00
  391. if(v && v.substr(0,1)=='+') {
  392. v1 = '+'; v = v.substr(1);
  393. }
  394. if(v && v.substr(0,2)=='00') {
  395. v1 += '00'; v = v.substr(2);
  396. }
  397. if(v && v.substr(0,1)=='0') {
  398. v1 += '0'; v = v.substr(1);
  399. }
  400. v1 += cint(v) + '';
  401. return v1;
  402. } else if(this.df.options == 'Email') {
  403. if(v+''=='')return '';
  404. if(!validate_email(v)) {
  405. msgprint(this.df.label + ': ' + v + ' is not a valid email id');
  406. return '';
  407. } else
  408. return v;
  409. } else {
  410. return v;
  411. }
  412. }
  413. DataField.prototype.onrefresh = function() {
  414. if(this.input&&this.df.colour) {
  415. var col = '#'+this.df.colour.split(':')[1];
  416. $bg(this.input,col);
  417. }
  418. }
  419. // ======================================================================================
  420. function ReadOnlyField() { }
  421. ReadOnlyField.prototype = new Field();
  422. // ======================================================================================
  423. function HTMLField() { }
  424. HTMLField.prototype = new Field();
  425. HTMLField.prototype.with_label = 0;
  426. HTMLField.prototype.set_disp = function(val) { this.disp_area.innerHTML = val; }
  427. HTMLField.prototype.set_input = function(val) { if(val) this.set_disp(val); }
  428. HTMLField.prototype.onrefresh = function() { this.set_disp(this.df.options?this.df.options:''); }
  429. // ======================================================================================
  430. var datepicker_active = 0;
  431. function DateField() { } DateField.prototype = new Field();
  432. DateField.prototype.make_input = function() {
  433. var me = this;
  434. this.user_fmt = wn.control_panel.date_format;
  435. if(!this.user_fmt)this.user_fmt = 'dd-mm-yy';
  436. this.input = $a(this.input_area, 'input');
  437. $(this.input).datepicker({
  438. dateFormat: me.user_fmt.replace('yyyy','yy'),
  439. altFormat:'yy-mm-dd',
  440. changeYear: true,
  441. beforeShow: function(input, inst) {
  442. datepicker_active = 1
  443. },
  444. onClose: function(dateText, inst) {
  445. datepicker_active = 0;
  446. if(_f.cur_grid_cell)
  447. _f.cur_grid_cell.grid.cell_deselect();
  448. }
  449. });
  450. var me = this;
  451. me.input.onchange = function() {
  452. // input as dd-mm-yyyy
  453. if(this.value==null)this.value='';
  454. if(!this.not_in_form)
  455. me.set(dateutil.user_to_str(me.input.value));
  456. me.run_trigger();
  457. }
  458. me.input.set_input = function(val) {
  459. if(val==null)val='';
  460. else val=dateutil.str_to_user(val);
  461. me.input.value = val;
  462. }
  463. me.get_value = function() {
  464. if(me.input.value)
  465. return dateutil.user_to_str(me.input.value);
  466. }
  467. }
  468. DateField.prototype.set_disp = function(val) {
  469. var v = dateutil.str_to_user(val);
  470. if(v==null)v = '';
  471. this.set_disp_html(v);
  472. }
  473. DateField.prototype.validate = function(v) {
  474. if(!v) return;
  475. var me = this;
  476. this.clear = function() {
  477. msgprint ("Date must be in format " + this.user_fmt);
  478. me.input.set_input('');
  479. return '';
  480. }
  481. var t = v.split('-');
  482. if(t.length!=3) { return this.clear(); }
  483. else if(cint(t[1])>12 || cint(t[1])<1) { return this.clear(); }
  484. else if(cint(t[2])>31 || cint(t[2])<1) { return this.clear(); }
  485. return v;
  486. };
  487. // ======================================================================================
  488. // reference when a new record is created via link
  489. function LinkField() { } LinkField.prototype = new Field();
  490. LinkField.prototype.make_input = function() {
  491. var me = this;
  492. if(me.df.no_buttons) {
  493. this.txt = $a(this.input_area, 'input');
  494. this.input = this.txt;
  495. } else {
  496. makeinput_popup(this, 'icon-search', 'icon-play', 'icon-plus');
  497. // setup buttons
  498. me.setup_buttons();
  499. me.onrefresh = function() {
  500. if(me.can_create && cur_frm.doc.docstatus==0)
  501. $(me.btn2).css('display', 'inline-block');
  502. else $dh(me.btn2);
  503. }
  504. }
  505. me.txt.field_object = this;
  506. // set onchange triggers
  507. me.input.set_input = function(val) {
  508. if(val==undefined)val='';
  509. me.txt.value = val;
  510. }
  511. me.get_value = function() { return me.txt.value; }
  512. $(me.txt).autocomplete({
  513. source: function(request, response) {
  514. wn.call({
  515. method:'webnotes.widgets.search.search_link',
  516. args: {
  517. 'txt': request.term,
  518. 'dt': me.df.options,
  519. 'query': me.get_custom_query()
  520. },
  521. callback: function(r) {
  522. response(r.results);
  523. },
  524. });
  525. },
  526. select: function(event, ui) {
  527. me.set_input_value(ui.item.value);
  528. return false;
  529. }
  530. }).data('autocomplete')._renderItem = function(ul, item) {
  531. return $('<li></li>')
  532. .data('item.autocomplete', item)
  533. .append(repl('<a>%(label)s<br><span style="font-size:10px">%(info)s</span></a>', item))
  534. .appendTo(ul);
  535. };
  536. }
  537. LinkField.prototype.get_custom_query = function() {
  538. this.set_get_query();
  539. if(this.get_query) {
  540. if(cur_frm)
  541. var doc = locals[cur_frm.doctype][cur_frm.docname];
  542. return this.get_query(doc, this.doctype, this.docname);
  543. }
  544. }
  545. LinkField.prototype.setup_buttons = function() {
  546. var me = this;
  547. // magnifier - search
  548. me.btn.onclick = function() {
  549. selector.set(me, me.df.options, me.df.label);
  550. selector.show(me.txt);
  551. }
  552. // open
  553. if(me.btn1)me.btn1.onclick = function() {
  554. if(me.txt.value && me.df.options) { loaddoc(me.df.options, me.txt.value); }
  555. }
  556. // add button - for inline creation of records
  557. me.can_create = 0;
  558. if((!me.not_in_form) && in_list(profile.can_create, me.df.options)) {
  559. me.can_create = 1;
  560. me.btn2.onclick = function() {
  561. var on_save_callback = function(new_rec) {
  562. if(new_rec) {
  563. var d = _f.calling_doc_stack.pop(); // patch for composites
  564. locals[d[0]][d[1]][me.df.fieldname] = new_rec;
  565. me.refresh();
  566. if(me.grid)me.grid.refresh();
  567. // call script
  568. me.run_trigger();
  569. }
  570. }
  571. _f.calling_doc_stack.push([me.doctype, me.docname]);
  572. new_doc(me.df.options, me.on_new, 1, on_save_callback, me.doctype, me.docname, me.frm.not_in_container);
  573. }
  574. } else {
  575. $dh(me.btn2); $y($td(me.tab,0,2), {width:'0px'});
  576. }
  577. }
  578. LinkField.prototype.set_input_value = function(val) {
  579. var me = this;
  580. // refresh mandatory style
  581. me.refresh_label_icon();
  582. // not in form, do nothing
  583. if(me.not_in_form) {
  584. $(this.txt).val(val);
  585. return;
  586. }
  587. // same value, do nothing
  588. if(cur_frm) {
  589. if(val == locals[me.doctype][me.docname][me.df.fieldname]) {
  590. me.set(val); // one more time, grid bug?
  591. me.run_trigger(); // wanted - called as refresh?
  592. return;
  593. }
  594. }
  595. // set in locals
  596. me.set(val);
  597. // deselect cell if in grid
  598. if(_f.cur_grid_cell)
  599. _f.cur_grid_cell.grid.cell_deselect();
  600. // run trigger if value is cleared
  601. if(!val) {
  602. me.run_trigger();
  603. return;
  604. }
  605. // validate the value just entered
  606. var fetch = '';
  607. if(cur_frm.fetch_dict[me.df.fieldname])
  608. fetch = cur_frm.fetch_dict[me.df.fieldname].columns.join(', ');
  609. $c('webnotes.widgets.form.utils.validate_link', {
  610. 'value':val,
  611. 'options':me.df.options,
  612. 'fetch': fetch
  613. },
  614. function(r,rt) {
  615. if(selector && selector.display) return; // selecting from popup
  616. if(r.message=='Ok') {
  617. // set fetch values
  618. if(r.fetch_values)
  619. me.set_fetch_values(r.fetch_values);
  620. me.run_trigger();
  621. } else {
  622. var astr = '';
  623. if(in_list(profile.can_create, me.df.options)) astr = repl('<br><br><span class="link_type" onclick="newdoc(\'%(dt)s\')">Click here</span> to create a new %(dtl)s', {dt:me.df.options, dtl:get_doctype_label(me.df.options)})
  624. msgprint(repl('error:<b>%(val)s</b> is not a valid %(dt)s.<br><br>You must first create a new %(dt)s <b>%(val)s</b> and then select its value. To find an existing %(dt)s, click on the magnifying glass next to the field.%(add)s', {val:me.txt.value, dt:get_doctype_label(me.df.options), add:astr}));
  625. me.txt.value = '';
  626. me.set('');
  627. }
  628. }
  629. );
  630. }
  631. LinkField.prototype.set_fetch_values = function(fetch_values) {
  632. var fl = cur_frm.fetch_dict[this.df.fieldname].fields;
  633. var changed_fields = [];
  634. for(var i=0; i< fl.length; i++) {
  635. if(locals[this.doctype][this.docname][fl[i]]!=fetch_values[i]) {
  636. locals[this.doctype][this.docname][fl[i]] = fetch_values[i];
  637. if(!this.grid) {
  638. refresh_field(fl[i]);
  639. // call trigger on the target field
  640. changed_fields.push(fl[i]);
  641. }
  642. }
  643. }
  644. // run triggers
  645. for(i=0; i<changed_fields.length; i++) {
  646. if(cur_frm.fields_dict[changed_fields[i]]) // on main
  647. cur_frm.fields_dict[changed_fields[i]].run_trigger();
  648. }
  649. // refresh grid
  650. if(this.grid) this.grid.refresh();
  651. }
  652. LinkField.prototype.set_get_query = function() {
  653. if(this.get_query)return;
  654. if(this.grid) {
  655. var f = this.grid.get_field(this.df.fieldname);
  656. if(f.get_query) this.get_query = f.get_query;
  657. }
  658. }
  659. LinkField.prototype.set_disp = function(val) {
  660. var t = null;
  661. if(val)t = "<a href=\'javascript:loaddoc(\""+this.df.options+"\", \""+val+"\")\'>"+val+"</a>";
  662. this.set_disp_html(t);
  663. }
  664. // ======================================================================================
  665. function IntField() { } IntField.prototype = new DataField();
  666. IntField.prototype.validate = function(v) {
  667. if(isNaN(parseInt(v)))return null;
  668. return cint(v);
  669. };
  670. IntField.prototype.format_input = function() {
  671. if(this.input.value==null) this.input.value='';
  672. }
  673. // ======================================================================================
  674. function FloatField() { } FloatField.prototype = new DataField();
  675. FloatField.prototype.validate = function(v) {
  676. var v= parseFloat(v); if(isNaN(v))return null;
  677. return v;
  678. };
  679. FloatField.prototype.format_input = function() {
  680. if(this.input.value==null) this.input.value='';
  681. }
  682. // ======================================================================================
  683. function CurrencyField() { } CurrencyField.prototype = new DataField();
  684. CurrencyField.prototype.format_input = function() {
  685. var v = fmt_money(this.input.value);
  686. if(this.not_in_form) {
  687. if(!flt(this.input.value)) v = ''; // blank in filter
  688. }
  689. this.input.value = v;
  690. }
  691. CurrencyField.prototype.validate = function(v) {
  692. if(v==null || v=='')
  693. return 0;
  694. return flt(v,2);
  695. }
  696. CurrencyField.prototype.set_disp = function(val) {
  697. var v = fmt_money(val);
  698. this.set_disp_html(v);
  699. }
  700. CurrencyField.prototype.onmake_input = function() {
  701. if(!this.input) return;
  702. this.input.onfocus = function() {
  703. if(flt(this.value)==0)this.select();
  704. }
  705. }
  706. // ======================================================================================
  707. function CheckField() { } CheckField.prototype = new Field();
  708. CheckField.prototype.validate = function(v) {
  709. var v= parseInt(v); if(isNaN(v))return 0;
  710. return v;
  711. };
  712. CheckField.prototype.onmake = function() {
  713. this.checkimg = $a(this.disp_area, 'div');
  714. var img = $a(this.checkimg, 'img');
  715. img.src = 'lib/images/ui/tick.gif';
  716. $dh(this.checkimg);
  717. }
  718. CheckField.prototype.make_input = function() { var me = this;
  719. this.input = $a_input(this.input_area,'checkbox');
  720. $y(this.input, {width:"16px", border:'0px', margin:'2px'}); // no specs for checkbox
  721. $(this.input).click(function() {
  722. me.set(this.checked?1:0);
  723. me.run_trigger();
  724. })
  725. this.input.set_input = function(v) {
  726. v = parseInt(v); if(isNaN(v)) v = 0;
  727. if(v) me.input.checked = true;
  728. else me.input.checked=false;
  729. }
  730. this.get_value= function() {
  731. return this.input.checked?1:0;
  732. }
  733. }
  734. CheckField.prototype.set_disp = function(val) {
  735. if (val){ $ds(this.checkimg); }
  736. else { $dh(this.checkimg); }
  737. }
  738. // ======================================================================================
  739. function TextField() { } TextField.prototype = new Field();
  740. TextField.prototype.set_disp = function(val) {
  741. this.disp_area.innerHTML = replace_newlines(val);
  742. }
  743. TextField.prototype.make_input = function() {
  744. var me = this;
  745. if(this.in_grid)
  746. return; // do nothing, text dialog will take over
  747. this.input = $a(this.input_area, 'textarea');
  748. if(this.df.fieldtype=='Small Text')
  749. this.input.style.height = "80px";
  750. this.input.set_input = function(v) {
  751. me.input.value = v;
  752. }
  753. this.input.onchange = function() {
  754. me.set(me.input.value);
  755. me.run_trigger();
  756. }
  757. this.get_value= function() {
  758. return this.input.value;
  759. }
  760. }
  761. // text dialog
  762. var text_dialog;
  763. function make_text_dialog() {
  764. var d = new Dialog(520,410,'Edit Text');
  765. d.make_body([
  766. ['Text', 'Enter Text'],
  767. ['HTML', 'Description'],
  768. ['Button', 'Update']
  769. ]);
  770. d.widgets['Update'].onclick = function() {
  771. var t = this.dialog;
  772. t.field.set(t.widgets['Enter Text'].value);
  773. t.hide();
  774. }
  775. d.onshow = function() {
  776. this.widgets['Enter Text'].style.height = '300px';
  777. var v = _f.get_value(this.field.doctype,this.field.docname,this.field.df.fieldname);
  778. this.widgets['Enter Text'].value = v==null?'':v;
  779. this.widgets['Enter Text'].focus();
  780. this.widgets['Description'].innerHTML = ''
  781. if(this.field.df.description)
  782. $a(this.widgets['Description'], 'div', 'field_description', '', this.field.df.description);
  783. }
  784. d.onhide = function() {
  785. if(_f.cur_grid_cell)
  786. _f.cur_grid_cell.grid.cell_deselect();
  787. }
  788. text_dialog = d;
  789. }
  790. TextField.prototype.table_refresh = function() {
  791. if(!this.text_dialog)
  792. make_text_dialog();
  793. text_dialog.set_title('Enter text for "'+ this.df.label +'"');
  794. text_dialog.field = this;
  795. text_dialog.show();
  796. }
  797. // Select
  798. // ======================================================================================
  799. function SelectField() { } SelectField.prototype = new Field();
  800. SelectField.prototype.make_input = function() {
  801. var me = this;
  802. var opt=[];
  803. if(this.in_filter && (!this.df.single_select)) {
  804. // multiple select
  805. this.input = $a(this.input_area, 'select');
  806. this.input.multiple = true;
  807. this.input.style.height = '4em';
  808. this.input.lab = $a(this.input_area, 'div', {fontSize:'9px',color:'#999'});
  809. this.input.lab.innerHTML = '(Use Ctrl+Click to select multiple or de-select)'
  810. } else {
  811. // Single select
  812. this.input = $a(this.input_area, 'select');
  813. this.input.onchange = function() {
  814. if(me.validate)
  815. me.validate();
  816. me.set(sel_val(this));
  817. me.run_trigger();
  818. }
  819. if(this.df.options == 'attach_files:') {
  820. this.file_attach = true;
  821. }
  822. }
  823. // set as single (to be called from report builder)
  824. this.set_as_single = function() {
  825. var i = this.input;
  826. i.multiple = false;
  827. i.style.height = null; // default
  828. if(i.lab)$dh(i.lab)
  829. }
  830. // refresh options list
  831. this.refresh_options = function(options) {
  832. if(options)
  833. me.df.options = options;
  834. if(this.file_attach)
  835. this.set_attach_options();
  836. me.options_list = me.df.options?me.df.options.split('\n'):[];
  837. // add options
  838. empty_select(this.input);
  839. if(me.in_filter && me.options_list[0]!='') {
  840. me.options_list = add_lists([''], me.options_list);
  841. }
  842. add_sel_options(this.input, me.options_list);
  843. }
  844. // refresh options
  845. this.onrefresh = function() {
  846. this.refresh_options();
  847. if(this.not_in_form) {
  848. this.input.value = '';
  849. return;
  850. }
  851. if(_f.get_value)
  852. var v = _f.get_value(this.doctype,this.docname,this.df.fieldname);
  853. else {
  854. if(this.options_list && this.options_list.length)
  855. var v = this.options_list[0];
  856. else
  857. var v = null;
  858. }
  859. this.input.set_input(v);
  860. }
  861. this.input.set_input=function(v) {
  862. if(!v) {
  863. if(!me.input.multiple) {
  864. if(me.docname) { // if called from onload without docname being set on fields
  865. if(me.options_list && me.options_list.length) {
  866. me.set(me.options_list[0]);
  867. me.input.value = me.options_list[0];
  868. } else {
  869. me.input.value = '';
  870. }
  871. }
  872. }
  873. } else {
  874. if(me.options_list) {
  875. if(me.input.multiple) {
  876. for(var i=0; i<me.input.options.length; i++) {
  877. me.input.options[i].selected = 0;
  878. if(me.input.options[i].value && inList(v.split(","), me.input.options[i].value))
  879. me.input.options[i].selected = 1;
  880. }
  881. } else if(in_list(me.options_list, v)){
  882. me.input.value = v;
  883. }
  884. }
  885. }
  886. }
  887. this.get_value= function() {
  888. if(me.input.multiple) {
  889. var l = [];
  890. for(var i=0;i<me.input.options.length; i++ ) {
  891. if(me.input.options[i].selected)l[l.length] = me.input.options[i].value;
  892. }
  893. return l;
  894. } else {
  895. if(me.input.options) {
  896. var val = sel_val(me.input);
  897. if(!val && !me.input.selectedIndex)
  898. val = me.input.options[0].value;
  899. return val;
  900. }
  901. return me.input.value;
  902. }
  903. }
  904. this.set_attach_options = function() {
  905. if(!cur_frm) return;
  906. var fl = cur_frm.doc.file_list;
  907. if(fl) {
  908. this.df.options = '';
  909. var fl = fl.split('\n');
  910. for(var i in fl) {
  911. this.df.options += '\n' + fl[i].split(',')[1];
  912. }
  913. } else {
  914. this.df.options = ''
  915. }
  916. }
  917. this.refresh();
  918. }
  919. // Time
  920. // ======================================================================================
  921. function TimeField() { } TimeField.prototype = new Field();
  922. TimeField.prototype.get_time = function() {
  923. return time_to_hhmm(sel_val(this.input_hr), sel_val(this.input_mn), sel_val(this.input_am));
  924. }
  925. TimeField.prototype.set_time = function(v) {
  926. //show_alert(ret);
  927. ret = time_to_ampm(v);
  928. this.input_hr.inp.value = ret[0];
  929. this.input_mn.inp.value = ret[1];
  930. this.input_am.inp.value = ret[2];
  931. }
  932. TimeField.prototype.set_style_mandatory = function() { }
  933. TimeField.prototype.set_as_error = function() { }
  934. TimeField.prototype.make_input = function() { var me = this;
  935. this.input = $a(this.input_area, 'div', 'time_field');
  936. var t = make_table(this.input, 1, 3, '200px');
  937. var opt_hr = ['1','2','3','4','5','6','7','8','9','10','11','12'];
  938. var opt_mn = ['00','05','10','15','20','25','30','35','40','45','50','55'];
  939. var opt_am = ['AM','PM'];
  940. this.input_hr = new SelectWidget($td(t,0,0), opt_hr, '50px');
  941. this.input_mn = new SelectWidget($td(t,0,1), opt_mn, '50px');
  942. this.input_am = new SelectWidget($td(t,0,2), opt_am, '50px');
  943. var onchange_fn = function() {
  944. me.set(me.get_time());
  945. me.run_trigger();
  946. }
  947. this.input_hr.inp.onchange = onchange_fn;
  948. this.input_mn.inp.onchange = onchange_fn;
  949. this.input_am.inp.onchange = onchange_fn;
  950. this.onrefresh = function() {
  951. var v = _f.get_value ? _f.get_value(me.doctype,me.docname,me.df.fieldname) : null;
  952. me.set_time(v);
  953. if(!v)
  954. me.set(me.get_time());
  955. }
  956. this.input.set_input=function(v) {
  957. if(v==null)v='';
  958. me.set_time(v);
  959. }
  960. this.get_value = function() {
  961. return this.get_time();
  962. }
  963. this.refresh();
  964. }
  965. TimeField.prototype.set_disp=function(v) {
  966. var t = time_to_ampm(v);
  967. var t = t[0]+':'+t[1]+' '+t[2];
  968. this.set_disp_html(t);
  969. }
  970. // ======================================================================================
  971. // Used by date and link fields
  972. function makeinput_popup(me, iconsrc, iconsrc1, iconsrc2) {
  973. var icon_style = {cursor: 'pointer', width: '16px', verticalAlign:'middle',
  974. marginBottom:'-3px'};
  975. me.input = $a(me.input_area, 'div');
  976. if(!me.not_in_form)
  977. $y(me.input, {width:'80%'});
  978. me.input.set_width = function(w) {
  979. $y(me.input, {width:(w-2)+'px'});
  980. }
  981. var tab = $a(me.input, 'table');
  982. me.tab = tab;
  983. $y(tab, {width:'100%', borderCollapse:'collapse', tableLayout:'fixed'});
  984. var c0 = tab.insertRow(0).insertCell(0);
  985. var c1 = tab.rows[0].insertCell(1);
  986. $y(c1,{width: '20px'});
  987. me.txt = $a($a($a(c0, 'div', '', {paddingRight:'8px'}), 'div'), 'input', '', {width:'100%'});
  988. me.btn = $a(c1, 'i', iconsrc, icon_style)
  989. if(iconsrc1) // link
  990. me.btn.setAttribute('title','Search');
  991. else // date
  992. me.btn.setAttribute('title','Select Date');
  993. if(iconsrc1) {
  994. var c2 = tab.rows[0].insertCell(2);
  995. $y(c2,{width: '20px'});
  996. me.btn1 = $a(c2, 'i', iconsrc1, icon_style)
  997. me.btn1.setAttribute('title','Open Link');
  998. }
  999. if(iconsrc2) {
  1000. var c3 = tab.rows[0].insertCell(3);
  1001. $y(c3,{width: '20px'});
  1002. me.btn2 = $a(c3, 'i', iconsrc2, icon_style)
  1003. me.btn2.setAttribute('title','Create New');
  1004. $dh(me.btn2);
  1005. }
  1006. if(me.df.colour)
  1007. me.txt.style.background = '#'+me.df.colour.split(':')[1];
  1008. me.txt.name = me.df.fieldname;
  1009. me.setdisabled = function(tf) { me.txt.disabled = tf; }
  1010. }
  1011. var tmpid = 0;
  1012. // ======================================================================================
  1013. _f.ButtonField = function() { };
  1014. _f.ButtonField.prototype = new Field();
  1015. _f.ButtonField.prototype.with_label = 0;
  1016. _f.ButtonField.prototype.init = function() {
  1017. this.prev_button = null;
  1018. // if previous field is a button, add it to the same div!
  1019. // button-set structure
  1020. // + wrapper (1st button)
  1021. // + input_area
  1022. // + button_area
  1023. // + button_area
  1024. // + button_area
  1025. if(!this.frm) return;
  1026. if(cur_frm &&
  1027. cur_frm.fields[cur_frm.fields.length-1] &&
  1028. cur_frm.fields[cur_frm.fields.length-1].df.fieldtype=='Button') {
  1029. this.make_body = function() {
  1030. this.prev_button = cur_frm.fields[cur_frm.fields.length-1];
  1031. if(!this.prev_button.prev_button) {
  1032. // first button, make the button area
  1033. this.prev_button.button_area = $a(this.prev_button.input_area, 'span');
  1034. }
  1035. this.wrapper = this.prev_button.wrapper;
  1036. this.input_area = this.prev_button.input_area;
  1037. this.disp_area = this.prev_button.disp_area;
  1038. // all buttons in the same input_area
  1039. this.button_area = $a(this.prev_button.input_area, 'span');
  1040. }
  1041. }
  1042. }
  1043. _f.ButtonField.prototype.make_input = function() { var me = this;
  1044. if(!this.prev_button) {
  1045. $y(this.input_area,{marginTop:'4px', marginBottom: '4px'});
  1046. }
  1047. // make a button area for one button
  1048. if(!this.button_area)
  1049. this.button_area = $a(this.input_area, 'span','',{marginRight:'4px'});
  1050. // make the input
  1051. this.input = $btn(this.button_area,
  1052. me.df.label, null,
  1053. {fontWeight:'bold'}, null, 1)
  1054. this.input.onclick = function() {
  1055. if(me.not_in_form) return;
  1056. this.disabled = 'disabled';
  1057. if(cur_frm.cscript[me.df.label] && (!me.in_filter)) {
  1058. cur_frm.runclientscript(me.df.label, me.doctype, me.docname);
  1059. this.disabled = false;
  1060. } else {
  1061. cur_frm.runscript(me.df.options, me);
  1062. this.disabled = false;
  1063. }
  1064. }
  1065. }
  1066. _f.ButtonField.prototype.hide = function() {
  1067. $dh(this.button_area);
  1068. };
  1069. _f.ButtonField.prototype.show = function() {
  1070. $ds(this.button_area);
  1071. };
  1072. _f.ButtonField.prototype.set = function(v) { }; // No Setter
  1073. _f.ButtonField.prototype.set_disp = function(val) { } // No Disp on readonly
  1074. // ======================================================================================
  1075. function make_field(docfield, doctype, parent, frm, in_grid, hide_label) { // Factory
  1076. switch(docfield.fieldtype.toLowerCase()) {
  1077. // general fields
  1078. case 'data':var f = new DataField(); break;
  1079. case 'password':var f = new DataField(); break;
  1080. case 'int':var f = new IntField(); break;
  1081. case 'float':var f = new FloatField(); break;
  1082. case 'currency':var f = new CurrencyField(); break;
  1083. case 'read only':var f = new ReadOnlyField(); break;
  1084. case 'link':var f = new LinkField(); break;
  1085. case 'date':var f = new DateField(); break;
  1086. case 'time':var f = new TimeField(); break;
  1087. case 'html':var f = new HTMLField(); break;
  1088. case 'check':var f = new CheckField(); break;
  1089. case 'text':var f = new TextField(); break;
  1090. case 'small text':var f = new TextField(); break;
  1091. case 'select':var f = new SelectField(); break;
  1092. case 'button':var f = new _f.ButtonField(); break;
  1093. // form fields
  1094. case 'code':var f = new _f.CodeField(); break;
  1095. case 'text editor':var f = new _f.CodeField(); break;
  1096. case 'table':var f = new _f.TableField(); break;
  1097. case 'section break':var f= new _f.SectionBreak(); break;
  1098. case 'column break':var f= new _f.ColumnBreak(); break;
  1099. case 'image':var f= new _f.ImageField(); break;
  1100. }
  1101. f.parent = parent;
  1102. f.doctype = doctype;
  1103. f.df = docfield;
  1104. f.perm = frm ? frm.perm : [[1,1,1]];
  1105. if(_f)
  1106. f.col_break_width = _f.cur_col_break_width;
  1107. if(in_grid) {
  1108. f.in_grid = true;
  1109. f.with_label = 0;
  1110. }
  1111. if(hide_label) {
  1112. f.with_label = 0;
  1113. }
  1114. if(frm) {
  1115. f.frm = frm;
  1116. if(parent)
  1117. f.layout_cell = parent.parentNode;
  1118. }
  1119. if(f.init) f.init();
  1120. f.make_body();
  1121. return f;
  1122. }