Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

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