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

1074 rader
29 KiB

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