Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 
 

1018 righe
27 KiB

  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. // MIT License. See license.txt
  3. wn.ui.form.make_control = function(opts) {
  4. var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, "");
  5. if(wn.ui.form[control_class_name]) {
  6. return new wn.ui.form[control_class_name](opts);
  7. } else {
  8. console.log("Invalid Control Name: " + opts.df.fieldtype);
  9. }
  10. }
  11. // old style
  12. function make_field(docfield, doctype, parent, frm, in_grid, hide_label) { // Factory
  13. return wn.ui.form.make_control({
  14. df: docfield,
  15. doctype: doctype,
  16. parent: parent,
  17. hide_label: hide_label,
  18. frm: frm
  19. });
  20. }
  21. wn.ui.form.Control = Class.extend({
  22. init: function(opts) {
  23. $.extend(this, opts);
  24. this.make();
  25. // if developer_mode=1, show fieldname as tooltip
  26. if(wn.boot.profile && wn.boot.profile.name==="Administrator" &&
  27. wn.boot.developer_mode===1 && this.$wrapper) {
  28. this.$wrapper.attr("title", wn._(this.df.fieldname));
  29. }
  30. },
  31. make: function() {
  32. this.make_wrapper();
  33. this.wrapper = this.$wrapper.get(0);
  34. this.wrapper.fieldobj = this; // reference for event handlers
  35. },
  36. make_wrapper: function() {
  37. this.$wrapper = $("<div>").appendTo(this.parent);
  38. },
  39. // returns "Read", "Write" or "None"
  40. // as strings based on permissions
  41. get_status: function(explain) {
  42. if(!this.doctype)
  43. return "Write";
  44. return wn.perm.get_field_display_status(this.df,
  45. locals[this.doctype][this.docname], this.perm || this.frm.perm, explain);
  46. },
  47. refresh: function() {
  48. this.disp_status = this.get_status();
  49. this.$wrapper
  50. && this.$wrapper.toggle(this.disp_status!="None")
  51. && this.$wrapper.trigger("refresh");
  52. },
  53. get_doc: function() {
  54. return this.doctype && this.docname
  55. && locals[this.doctype] && locals[this.doctype][this.docname] || {};
  56. },
  57. parse_validate_and_set_in_model: function(value) {
  58. var me = this;
  59. this.inside_change_event = true;
  60. if(this.parse) value = this.parse(value);
  61. var set = function(value) {
  62. me.set_model_value(value);
  63. me.inside_change_event = false;
  64. me.set_mandatory && me.set_mandatory(value);
  65. }
  66. this.validate ? this.validate(value, set) : set(value);
  67. },
  68. get_parsed_value: function() {
  69. var me = this;
  70. return this.get_value ?
  71. (this.parse ? this.parse(this.get_value()) : this.get_value()) :
  72. undefined;
  73. },
  74. set_model_value: function(value) {
  75. if(wn.model.set_value(this.doctype, this.docname, this.df.fieldname,
  76. value, this.df.fieldtype)) {
  77. this.last_value = value;
  78. }
  79. },
  80. });
  81. wn.ui.form.ControlHTML = wn.ui.form.Control.extend({
  82. make: function() {
  83. this._super();
  84. var me = this;
  85. this.disp_area = this.wrapper;
  86. this.$wrapper.on("refresh", function() {
  87. if(me.df.options)
  88. me.$wrapper.html(me.df.options);
  89. })
  90. }
  91. });
  92. wn.ui.form.ControlImage = wn.ui.form.Control.extend({
  93. make: function() {
  94. this._super();
  95. var me = this;
  96. this.$wrapper
  97. .css({"margin-bottom": "10px", "margin-right": "15px", "float": "right",
  98. "text-align": "right", "max-width": "100%"})
  99. .on("refresh", function() {
  100. me.$wrapper.empty();
  101. if(me.df.options && me.frm.doc[me.df.options]) {
  102. $("<img src='"+me.frm.doc[me.df.options]+"' style='max-width: 70%;'>")
  103. .appendTo(me.$wrapper);
  104. } else {
  105. $("<div class='missing-image'><i class='icon-camera'></i></div>")
  106. .appendTo(me.$wrapper)
  107. }
  108. return false;
  109. });
  110. }
  111. });
  112. wn.ui.form.ControlInput = wn.ui.form.Control.extend({
  113. horizontal: true,
  114. make: function() {
  115. // parent element
  116. this._super();
  117. this.set_input_areas();
  118. // set description
  119. this.set_max_width();
  120. this.setup_update_on_refresh();
  121. },
  122. make_wrapper: function() {
  123. if(this.only_input) {
  124. this.$wrapper = $('<div class="form-group">').appendTo(this.parent);
  125. } else {
  126. this.$wrapper = $('<div class="form-horizontal">\
  127. <div class="form-group row" style="margin: 0px">\
  128. <label class="control-label small col-xs-'+(this.horizontal?"4":"12")
  129. +'" style="padding-right: 0px;"></label>\
  130. <div class="col-xs-'+(this.horizontal?"8":"12")+'">\
  131. <div class="control-input"></div>\
  132. <div class="control-value like-disabled-input" style="display: none;"></div>\
  133. <p class="help-box small text-muted"></p>\
  134. </div>\
  135. </div>\
  136. </div>').appendTo(this.parent);
  137. if(!this.horizontal) {
  138. this.$wrapper.removeClass("form-horizontal");
  139. }
  140. }
  141. },
  142. set_input_areas: function() {
  143. if(this.only_input) {
  144. this.input_area = this.wrapper;
  145. } else {
  146. this.label_area = this.label_span = this.$wrapper.find("label").get(0);
  147. this.input_area = this.$wrapper.find(".control-input").get(0);
  148. // keep a separate display area to rendered formatted values
  149. // like links, currencies, HTMLs etc.
  150. this.disp_area = this.$wrapper.find(".control-value").get(0);
  151. }
  152. },
  153. set_max_width: function() {
  154. if(this.horizontal) {
  155. this.$wrapper.css({"max-width": "600px"});
  156. }
  157. },
  158. // update input value, label, description
  159. // display (show/hide/read-only),
  160. // mandatory style on refresh
  161. setup_update_on_refresh: function() {
  162. var me = this;
  163. this.$wrapper.on("refresh", function() {
  164. if(me.disp_status != "None") {
  165. // refresh value
  166. if(me.doctype && me.docname) {
  167. me.value = wn.model.get_value(me.doctype, me.docname, me.df.fieldname);
  168. }
  169. if(me.disp_status=="Write") {
  170. me.disp_area && $(me.disp_area).toggle(false);
  171. $(me.input_area).toggle(true);
  172. $(me.input_area).find("input").prop("disabled", false);
  173. !me.has_input && me.make_input();
  174. if(me.doctype && me.docname)
  175. me.set_input(me.value);
  176. } else {
  177. $(me.input_area).toggle(false);
  178. $(me.input_area).find("input").prop("disabled", true);
  179. me.disp_area && $(me.disp_area)
  180. .toggle(true)
  181. .html(
  182. wn.format(me.value, me.df, {no_icon:true}, locals[me.doctype][me.name])
  183. );
  184. }
  185. me.set_description();
  186. me.set_label();
  187. me.set_mandatory(me.value);
  188. }
  189. return false;
  190. });
  191. },
  192. bind_change_event: function() {
  193. var me = this;
  194. this.$input && this.$input.on("change", this.change || function(e) {
  195. if(me.doctype && me.docname && me.get_value) {
  196. me.parse_validate_and_set_in_model(me.get_value());
  197. } else {
  198. // inline
  199. var value = me.get_value();
  200. if(me.parse) {
  201. value = me.parse(value);
  202. }
  203. if(me.validate) {
  204. me.validate(value, function(value1) {
  205. if(value !== value1)
  206. me.set_input(value1)
  207. });
  208. }
  209. }
  210. });
  211. },
  212. set_label: function(label) {
  213. if(label) this.df.label = label;
  214. if(this.only_input || this.df.label==this._label)
  215. return;
  216. // var icon = wn.ui.form.fieldtype_icons[this.df.fieldtype];
  217. // if(this.df.fieldtype==="Link") {
  218. // icon = wn.boot.doctype_icons[this.df.options];
  219. // } else if(this.df.link_doctype) {
  220. // icon = wn.boot.doctype_icons[this.df.link_doctype];
  221. // }
  222. var icon = "";
  223. this.label_span.innerHTML = (icon ? '<i class="'+icon+'"></i> ' : "") +
  224. wn._(this.df.label) || "&nbsp;";
  225. this._label = this.df.label;
  226. },
  227. set_description: function() {
  228. if(this.only_input || this.df.description===this._description)
  229. return;
  230. if(this.df.description) {
  231. this.$wrapper.find(".help-box").html(wn._(this.df.description));
  232. } else {
  233. this.set_empty_description();
  234. }
  235. this._description = this.df.description;
  236. },
  237. set_empty_description: function() {
  238. this.$wrapper.find(".help-box").html("");
  239. },
  240. set_mandatory: function(value) {
  241. this.$wrapper.toggleClass("has-error", (this.df.reqd
  242. && (value==null || value==="")) ? true : false);
  243. },
  244. });
  245. wn.ui.form.ControlData = wn.ui.form.ControlInput.extend({
  246. html_element: "input",
  247. input_type: "text",
  248. make_input: function() {
  249. this.$input = $("<"+ this.html_element +">")
  250. .attr("type", this.input_type)
  251. .addClass("input-with-feedback form-control")
  252. .prependTo(this.input_area)
  253. this.set_input_attributes();
  254. this.input = this.$input.get(0);
  255. this.has_input = true;
  256. this.bind_change_event();
  257. },
  258. set_input_attributes: function() {
  259. this.$input
  260. .attr("data-fieldtype", this.df.fieldtype)
  261. .attr("data-fieldname", this.df.fieldname)
  262. .attr("placeholder", this.df.placeholder || "")
  263. if(this.doctype)
  264. this.$input.attr("data-doctype", this.doctype);
  265. if(this.df.input_css)
  266. this.$input.css(this.df.input_css);
  267. },
  268. set_input: function(val) {
  269. this.$input.val(this.format_for_input(val));
  270. this.last_value = val;
  271. this.set_mandatory && this.set_mandatory(val);
  272. },
  273. get_value: function() {
  274. return this.$input ? this.$input.val() : undefined;
  275. },
  276. format_for_input: function(val) {
  277. return val==null ? "" : val;
  278. },
  279. validate: function(v, callback) {
  280. if(this.df.options == 'Phone') {
  281. if(v+''=='')return '';
  282. v1 = ''
  283. // phone may start with + and must only have numbers later, '-' and ' ' are stripped
  284. v = v.replace(/ /g, '').replace(/-/g, '').replace(/\(/g, '').replace(/\)/g, '');
  285. // allow initial +,0,00
  286. if(v && v.substr(0,1)=='+') {
  287. v1 = '+'; v = v.substr(1);
  288. }
  289. if(v && v.substr(0,2)=='00') {
  290. v1 += '00'; v = v.substr(2);
  291. }
  292. if(v && v.substr(0,1)=='0') {
  293. v1 += '0'; v = v.substr(1);
  294. }
  295. v1 += cint(v) + '';
  296. callback(v1);
  297. } else if(this.df.options == 'Email') {
  298. if(v+''=='')return '';
  299. if(!validate_email(v)) {
  300. msgprint(wn._("Invalid Email") + ": " + v);
  301. callback("");
  302. } else
  303. callback(v);
  304. } else {
  305. callback(v);
  306. }
  307. }
  308. });
  309. wn.ui.form.ControlReadOnly = wn.ui.form.ControlData.extend({
  310. get_status: function(explain) {
  311. var status = this._super(explain);
  312. if(status==="Write")
  313. status = "Read";
  314. return;
  315. }
  316. });
  317. wn.ui.form.ControlPassword = wn.ui.form.ControlData.extend({
  318. input_type: "password"
  319. });
  320. wn.ui.form.ControlInt = wn.ui.form.ControlData.extend({
  321. make_input: function() {
  322. var me = this;
  323. this._super();
  324. this.$input
  325. .css({"text-align": "right"})
  326. .on("focus", function() {
  327. setTimeout(function() {
  328. if(!document.activeElement) return;
  329. me.validate(document.activeElement.value, function(val) {
  330. document.activeElement.value = val;
  331. });
  332. document.activeElement.select()
  333. }, 100);
  334. return false;
  335. })
  336. },
  337. validate: function(value, callback) {
  338. return callback(cint(value, null));
  339. }
  340. });
  341. wn.ui.form.ControlFloat = wn.ui.form.ControlInt.extend({
  342. validate: function(value, callback) {
  343. return callback(isNaN(parseFloat(value)) ? null : flt(value));
  344. },
  345. format_for_input: function(value) {
  346. var formatted_value = format_number(parseFloat(value),
  347. null, cint(wn.boot.sysdefaults.float_precision, null));
  348. return isNaN(parseFloat(value)) ? "" : formatted_value;
  349. }
  350. });
  351. wn.ui.form.ControlCurrency = wn.ui.form.ControlFloat.extend({
  352. format_for_input: function(value) {
  353. var formatted_value = format_number(parseFloat(value),
  354. get_number_format(this.get_currency()));
  355. return isNaN(parseFloat(value)) ? "" : formatted_value;
  356. },
  357. get_currency: function() {
  358. return wn.meta.get_field_currency(this.df, this.get_doc());
  359. }
  360. });
  361. wn.ui.form.ControlPercent = wn.ui.form.ControlFloat;
  362. wn.ui.form.ControlDate = wn.ui.form.ControlData.extend({
  363. datepicker_options: {
  364. altFormat:'yy-mm-dd',
  365. changeYear: true,
  366. yearRange: "-70Y:+10Y",
  367. },
  368. make_input: function() {
  369. this._super();
  370. this.set_datepicker();
  371. },
  372. set_datepicker: function() {
  373. this.datepicker_options.dateFormat =
  374. (wn.boot.sysdefaults.date_format || 'yyyy-mm-dd').replace("yyyy", "yy")
  375. this.$input.datepicker(this.datepicker_options);
  376. },
  377. parse: function(value) {
  378. return value ? dateutil.user_to_str(value) : value;
  379. },
  380. format_for_input: function(value) {
  381. return value ? dateutil.str_to_user(value) : "";
  382. },
  383. validate: function(value, callback) {
  384. var value = wn.datetime.validate(value);
  385. if(!value) {
  386. msgprint (wn._("Date must be in format") + ": " + (sys_defaults.date_format || "yyyy-mm-dd"));
  387. callback("");
  388. }
  389. return callback(value);
  390. }
  391. })
  392. import_timepicker = function() {
  393. wn.require("assets/webnotes/js/lib/jquery/jquery.ui.slider.min.js");
  394. wn.require("assets/webnotes/js/lib/jquery/jquery.ui.sliderAccess.js");
  395. wn.require("assets/webnotes/js/lib/jquery/jquery.ui.timepicker-addon.css");
  396. wn.require("assets/webnotes/js/lib/jquery/jquery.ui.timepicker-addon.js");
  397. }
  398. wn.ui.form.ControlTime = wn.ui.form.ControlData.extend({
  399. make_input: function() {
  400. import_timepicker();
  401. this._super();
  402. this.$input.timepicker({
  403. timeFormat: 'hh:mm:ss',
  404. });
  405. }
  406. });
  407. wn.ui.form.ControlDatetime = wn.ui.form.ControlDate.extend({
  408. set_datepicker: function() {
  409. this.datepicker_options.timeFormat = "hh:mm:ss";
  410. this.datepicker_options.dateFormat =
  411. (wn.boot.sysdefaults.date_format || 'yy-mm-dd').replace('yyyy','yy');
  412. this.$input.datetimepicker(this.datepicker_options);
  413. },
  414. make_input: function() {
  415. import_timepicker();
  416. this._super();
  417. },
  418. });
  419. wn.ui.form.ControlText = wn.ui.form.ControlData.extend({
  420. html_element: "textarea",
  421. horizontal: false
  422. });
  423. wn.ui.form.ControlLongText = wn.ui.form.ControlText;
  424. wn.ui.form.ControlSmallText = wn.ui.form.ControlText;
  425. wn.ui.form.ControlCheck = wn.ui.form.ControlData.extend({
  426. input_type: "checkbox",
  427. make_wrapper: function() {
  428. this.$wrapper = $('<div class="form-group row" style="margin: 0px;">\
  429. <div class="col-md-offset-4 col-md-8">\
  430. <div class="checkbox" style="margin: 5px 0px">\
  431. <label>\
  432. <span class="input-area"></span>\
  433. <span class="disp-area" style="display:none;"></span>\
  434. <span class="label-area small"></span>\
  435. </label>\
  436. <p class="help-box small text-muted"></p>\
  437. </div>\
  438. </div>\
  439. </div>').appendTo(this.parent)
  440. },
  441. set_input_areas: function() {
  442. this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0);
  443. this.input_area = this.$wrapper.find(".input-area").get(0);
  444. this.disp_area = this.$wrapper.find(".disp-area").get(0);
  445. },
  446. make_input: function() {
  447. this._super();
  448. this.$input.removeClass("form-control");
  449. },
  450. parse: function(value) {
  451. return this.input.checked ? 1 : 0;
  452. },
  453. validate: function(value, callback) {
  454. return callback(cint(value));
  455. },
  456. set_input: function(value) {
  457. this.input.checked = value ? 1 : 0;
  458. this.last_value = value;
  459. }
  460. });
  461. wn.ui.form.ControlButton = wn.ui.form.ControlData.extend({
  462. make_input: function() {
  463. var me = this;
  464. this.$input = $('<button class="btn btn-default">')
  465. .prependTo(me.input_area)
  466. .on("click", function() {
  467. me.onclick();
  468. });
  469. this.input = this.$input.get(0);
  470. this.set_input_attributes();
  471. this.has_input = true;
  472. },
  473. onclick: function() {
  474. if(this.frm && this.frm.doc && this.frm.cscript) {
  475. if(this.frm.cscript[this.df.fieldname]) {
  476. this.frm.script_manager.trigger(this.df.fieldname, this.doctype, this.docname);
  477. } else {
  478. this.frm.runscript(this.df.options, this);
  479. }
  480. }
  481. else if(this.df.click) {
  482. this.df.click();
  483. }
  484. },
  485. set_input_areas: function() {
  486. this._super();
  487. $(this.disp_area).removeClass();
  488. },
  489. set_empty_description: function() {
  490. this.$wrapper.find(".help-box").empty().toggle(false);
  491. },
  492. set_label: function() {
  493. $(this.label_span).html("&nbsp;");
  494. this.$input && this.$input.html(this.df.label);
  495. }
  496. });
  497. wn.ui.form.ControlAttach = wn.ui.form.ControlData.extend({
  498. make_input: function() {
  499. var me = this;
  500. this.$input = $('<button class="btn btn-default">')
  501. .html(wn._("Upload"))
  502. .prependTo(me.input_area)
  503. .on("click", function() {
  504. me.onclick();
  505. });
  506. this.$value = $('<div class="alert alert-info">\
  507. <a class="attached-file text-ellipsis" style="width: 80%" target="_blank"></a>\
  508. <a class="close">&times;</a></div>')
  509. .prependTo(me.input_area)
  510. .toggle(false);
  511. this.input = this.$input.get(0);
  512. this.set_input_attributes();
  513. this.has_input = true;
  514. this.$value.find(".close").on("click", function() {
  515. if(me.frm) {
  516. me.frm.attachments.remove_attachment_by_filename(me.value, function() {
  517. me.parse_validate_and_set_in_model(null);
  518. me.refresh();
  519. });
  520. } else {
  521. me.dataurl = null;
  522. me.fileobj = null;
  523. me.set_input(null);
  524. me.refresh();
  525. }
  526. })
  527. },
  528. onclick: function() {
  529. if(!this.dialog) {
  530. this.dialog = new wn.ui.Dialog({
  531. title: wn._(this.df.label || wn._("Upload")),
  532. });
  533. }
  534. $(this.dialog.body).empty();
  535. this.set_upload_options();
  536. wn.upload.make(this.upload_options);
  537. this.dialog.show();
  538. },
  539. set_upload_options: function() {
  540. var me = this;
  541. this.upload_options = {
  542. parent: this.dialog.body,
  543. args: {},
  544. max_width: this.df.max_width,
  545. max_height: this.df.max_height,
  546. callback: function(fileid, filename, r) {
  547. me.dialog.hide();
  548. me.on_upload_complete(fileid, filename, r);
  549. },
  550. onerror: function() {
  551. me.dialog.hide();
  552. },
  553. }
  554. if(this.frm) {
  555. this.upload_options.args = {
  556. from_form: 1,
  557. doctype: this.frm.doctype,
  558. docname: this.frm.docname,
  559. }
  560. } else {
  561. this.upload_options.on_attach = function(fileobj, dataurl) {
  562. me.dialog.hide();
  563. me.fileobj = fileobj;
  564. me.dataurl = dataurl;
  565. if(me.on_attach) {
  566. me.on_attach()
  567. }
  568. if(me.df.on_attach) {
  569. me.df.on_attach(fileobj, dataurl);
  570. }
  571. me.on_upload_complete();
  572. }
  573. }
  574. },
  575. set_input: function(value, dataurl) {
  576. this.value = value;
  577. if(this.value) {
  578. this.$input.toggle(false);
  579. this.$value.toggle(true).find(".attached-file")
  580. .html(this.value)
  581. .attr("href", dataurl || this.value);
  582. } else {
  583. this.$input.toggle(true);
  584. this.$value.toggle(false);
  585. }
  586. },
  587. get_value: function() {
  588. if(this.frm) {
  589. return this.value;
  590. } else {
  591. return this.fileobj ? (this.fileobj.filename + "," + this.dataurl) : null;
  592. }
  593. },
  594. on_upload_complete: function(fileid, filename, r) {
  595. if(this.frm) {
  596. this.parse_validate_and_set_in_model(filename);
  597. this.refresh();
  598. this.frm.attachments.update_attachment(fileid, filename, this.df.fieldname, r);
  599. } else {
  600. this.set_input(this.fileobj.filename, this.dataurl);
  601. this.refresh();
  602. }
  603. },
  604. });
  605. wn.ui.form.ControlAttachImage = wn.ui.form.ControlAttach.extend({
  606. make_input: function() {
  607. this._super();
  608. this.img = $("<img class='img-responsive'>").appendTo($('<div style="margin: 7px 0px;">\
  609. <div class="missing-image"><i class="icon-camera"></i></div></div>')
  610. .prependTo(this.input_area)).toggle(false);
  611. var me = this;
  612. this.$wrapper.on("refresh", function() {
  613. if(me.value) {
  614. $(me.input_area).find(".missing-image").toggle(false);
  615. me.img.attr("src", me.dataurl ? me.dataurl : me.value).toggle(true);
  616. } else {
  617. $(me.input_area).find(".missing-image").toggle(true);
  618. me.img.toggle(false);
  619. }
  620. });
  621. },
  622. });
  623. wn.ui.form.ControlSelect = wn.ui.form.ControlData.extend({
  624. html_element: "select",
  625. make_input: function() {
  626. var me = this;
  627. this._super();
  628. if(this.df.options=="attach_files:") {
  629. this.setup_attachment();
  630. }
  631. this.set_options();
  632. },
  633. set_input: function(value) {
  634. // refresh options first - (new ones??)
  635. this.set_options(value || "");
  636. this._super(value);
  637. // not a possible option, repair
  638. if(this.doctype && this.docname) {
  639. // model value is not an option,
  640. // set the default option (displayed)
  641. var input_value = this.$input.val();
  642. var model_value = wn.model.get_value(this.doctype, this.docname, this.df.fieldname);
  643. if(model_value == null && input_value != (model_value || "")) {
  644. this.set_model_value(input_value);
  645. } else {
  646. this.last_value = value;
  647. }
  648. }
  649. },
  650. setup_attachment: function() {
  651. var me = this;
  652. $(this.input).css({"width": "85%", "display": "inline-block"});
  653. this.$attach = $("<button class='btn btn-default' title='"+ wn._("Add attachment") + "'\
  654. style='padding-left: 6px; padding-right: 6px; margin-right: 6px;'>\
  655. <i class='icon-plus'></i></button>")
  656. .click(function() {
  657. me.frm.attachments.new_attachment(me.df.fieldname);
  658. })
  659. .prependTo(this.input_area);
  660. $(document).on("upload_complete", function(event, filename, file_url) {
  661. if(cur_frm === me.frm) {
  662. me.set_options();
  663. }
  664. })
  665. this.$wrapper.on("refresh", function() {
  666. me.$attach.toggle(!me.frm.doc.__islocal);
  667. });
  668. },
  669. set_options: function(value) {
  670. var options = this.df.options || [];
  671. if(this.df.options=="attach_files:") {
  672. options = this.get_file_attachment_list();
  673. } else if(typeof this.df.options==="string") {
  674. options = this.df.options.split("\n");
  675. }
  676. if(this.in_filter && options[0] != "") {
  677. options = add_lists([''], options);
  678. }
  679. var selected = this.$input.find(":selected").val();
  680. this.$input.empty().add_options(options || []);
  681. if(value===undefined && selected) this.$input.val(selected);
  682. },
  683. get_file_attachment_list: function() {
  684. if(!this.frm) return;
  685. var fl = wn.model.docinfo[this.frm.doctype][this.frm.docname];
  686. if(fl && fl.attachments) {
  687. fl = fl.attachments;
  688. this.set_description("");
  689. var options = [""];
  690. for(var fname in fl) {
  691. if(fname.indexOf("/")===-1)
  692. fname = "files/" + fname;
  693. options.push(fname);
  694. }
  695. return options;
  696. } else {
  697. this.set_description(wn._("Please attach a file first."))
  698. return [""];
  699. }
  700. }
  701. });
  702. // special features for link
  703. // buttons
  704. // autocomplete
  705. // link validation
  706. // custom queries
  707. // add_fetches
  708. wn.ui.form.ControlLink = wn.ui.form.ControlData.extend({
  709. make_input: function() {
  710. var me = this;
  711. $('<div class="link-field" style="display: table; width: 100%;">\
  712. <input type="text" class="input-with-feedback form-control" \
  713. style="display: table-cell">\
  714. <span class="link-field-btn" style="display: table-cell">\
  715. <a class="btn-search" title="Search Link">\
  716. <i class="icon-search"></i>\
  717. </a><a class="btn-open" title="Open Link">\
  718. <i class="icon-play"></i>\
  719. </a><a class="btn-new" title="Make New">\
  720. <i class="icon-plus"></i>\
  721. </a>\
  722. </span>\
  723. </div>').prependTo(this.input_area);
  724. this.$input_area = $(this.input_area);
  725. this.$input = this.$input_area.find('input');
  726. this.set_input_attributes();
  727. this.$input.on("focus", function() {
  728. setTimeout(function() {
  729. if(!me.$input.val()) {
  730. me.$input.val("%").trigger("keydown");
  731. }
  732. }, 1000)
  733. })
  734. this.input = this.$input.get(0);
  735. this.has_input = true;
  736. //this.bind_change_event();
  737. var me = this;
  738. this.setup_buttons();
  739. //this.setup_typeahead();
  740. this.setup_autocomplete();
  741. },
  742. setup_buttons: function() {
  743. var me = this;
  744. // magnifier - search
  745. this.$input_area.find(".btn-search").on("click", function() {
  746. new wn.ui.form.LinkSelector({
  747. doctype: me.df.options,
  748. target: me,
  749. txt: me.get_value()
  750. });
  751. });
  752. // open
  753. if(wn.model.can_read(me.df.options)) {
  754. this.$input_area.find(".btn-open").on("click", function() {
  755. var value = me.get_value();
  756. if(value && me.df.options) wn.set_route("Form", me.df.options, value);
  757. });
  758. } else {
  759. this.$input_area.find(".btn-open").remove();
  760. }
  761. // new
  762. if(wn.model.can_create(me.df.options)) {
  763. this.$input_area.find(".btn-new").on("click", function() {
  764. me.frm.new_doc(me.df.options, me);
  765. });
  766. } else {
  767. this.$input_area.find(".btn-new").remove();
  768. }
  769. if(this.only_input) this.$input_area.find(".btn-open, .btn-new").remove();
  770. },
  771. setup_autocomplete: function() {
  772. var me = this;
  773. this.$input.on("blur", function() {
  774. if(me.selected) {
  775. me.selected = false;
  776. return;
  777. }
  778. if(me.doctype && me.docname) {
  779. var value = me.get_value();
  780. if(value!==me.last_value) {
  781. me.parse_validate_and_set_in_model(value);
  782. }
  783. }});
  784. this.$input.autocomplete({
  785. source: function(request, response) {
  786. var args = {
  787. 'txt': request.term,
  788. 'doctype': me.df.options,
  789. };
  790. me.set_custom_query(args);
  791. return wn.call({
  792. type: "GET",
  793. method:'webnotes.widgets.search.search_link',
  794. no_spinner: true,
  795. args: args,
  796. callback: function(r) {
  797. response(r.results);
  798. },
  799. });
  800. },
  801. open: function(event, ui) {
  802. me.autocomplete_open = true;
  803. },
  804. close: function(event, ui) {
  805. me.autocomplete_open = false;
  806. },
  807. select: function(event, ui) {
  808. me.autocomplete_open = false;
  809. if(me.frm && me.frm.doc) {
  810. me.selected = true;
  811. me.parse_validate_and_set_in_model(ui.item.value);
  812. } else {
  813. me.$input.val(ui.item.value);
  814. me.$input.trigger("change");
  815. }
  816. }
  817. }).data('uiAutocomplete')._renderItem = function(ul, d) {
  818. var html = "";
  819. if(keys(d).length > 1) {
  820. d.info = $.map(d, function(val, key) { return ["value", "label"].indexOf(key)!==-1 ? null : val }).join(", ") || "";
  821. html = repl("<a>%(value)s<br><span class='text-muted'>%(info)s</span></a>", d);
  822. } else {
  823. html = "<a>" + d.value + "</a>";
  824. }
  825. return $('<li></li>')
  826. .data('item.autocomplete', d)
  827. .append(html)
  828. .appendTo(ul);
  829. };
  830. // remove accessibility span (for now)
  831. this.$wrapper.find(".ui-helper-hidden-accessible").remove();
  832. },
  833. set_custom_query: function(args) {
  834. var set_nulls = function(obj) {
  835. $.each(obj, function(key, value) {
  836. if(value!==undefined) {
  837. obj[key] = value || null;
  838. }
  839. });
  840. return obj;
  841. }
  842. if(this.get_query || this.df.get_query) {
  843. var get_query = this.get_query || this.df.get_query;
  844. if($.isPlainObject(get_query)) {
  845. $.extend(args, set_nulls(get_query));
  846. } else if(typeof(get_query)==="string") {
  847. args.query = get_query;
  848. } else {
  849. var q = (get_query)(this.frm && this.frm.doc, this.doctype, this.docname);
  850. if (typeof(q)==="string") {
  851. args.query = q;
  852. } else if($.isPlainObject(q)) {
  853. if(q.filters) {
  854. set_nulls(q.filters);
  855. }
  856. $.extend(args, q);
  857. }
  858. }
  859. }
  860. },
  861. validate: function(value, callback) {
  862. // validate the value just entered
  863. var me = this;
  864. if(this.df.options=="[Select]") {
  865. callback(value);
  866. return;
  867. }
  868. this.frm.script_manager.validate_link_and_fetch(this.df, this.docname, value, callback);
  869. },
  870. });
  871. wn.ui.form.ControlCode = wn.ui.form.ControlText.extend({
  872. make_input: function() {
  873. this._super();
  874. $(this.input_area).find("textarea").css({"height":"400px", "font-family": "Monaco, \"Courier New\", monospace"});
  875. }
  876. });
  877. wn.ui.form.ControlTextEditor = wn.ui.form.ControlCode.extend({
  878. editor_name: "bsEditor",
  879. horizontal: false,
  880. make_input: function() {
  881. $(this.input_area).css({"min-height":"360px"});
  882. var me = this;
  883. this.editor = new (wn.provide(this.editor_name))({
  884. parent: this.input_area,
  885. change: function(value) {
  886. me.parse_validate_and_set_in_model(value);
  887. },
  888. field: this
  889. });
  890. this.has_input = true;
  891. this.editor.editor.keypress("ctrl+s meta+s", function() {
  892. me.frm.save_or_update();
  893. });
  894. },
  895. get_value: function() {
  896. return this.editor.get_value();
  897. },
  898. set_input: function(value) {
  899. this.editor.set_input(value);
  900. this.last_value = value;
  901. }
  902. });
  903. wn.ui.form.ControlTable = wn.ui.form.Control.extend({
  904. make: function() {
  905. this._super();
  906. // add title if prev field is not column / section heading or html
  907. var prev_fieldtype = wn.model.get("DocField",
  908. {parent: this.frm.doctype, idx: this.df.idx-1})[0].fieldtype;
  909. if(["Column Break", "Section Break", "HTML"].indexOf(prev_fieldtype)===-1) {
  910. $("<label>" + this.df.label + "<label>").appendTo(this.wrapper);
  911. }
  912. this.grid = new wn.ui.form.Grid({
  913. frm: this.frm,
  914. df: this.df,
  915. perm: this.perm || this.frm.perm,
  916. parent: this.wrapper
  917. })
  918. if(this.frm)
  919. this.frm.grids[this.frm.grids.length] = this;
  920. // description
  921. if(this.df.description) {
  922. $('<p class="text-muted small">' + wn._(this.df.description) + '</p>')
  923. .appendTo(this.wrapper);
  924. }
  925. var me = this;
  926. this.$wrapper.on("refresh", function() {
  927. me.grid.refresh();
  928. return false;
  929. });
  930. }
  931. })
  932. wn.ui.form.fieldtype_icons = {
  933. "Date": "icon-calendar",
  934. "Time": "icon-time",
  935. "Datetime": "icon-time",
  936. "Code": "icon-code",
  937. "Select": "icon-flag"
  938. };