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

759 line
20 KiB

  1. wn.ui.form.make_control = function(opts) {
  2. var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, "");
  3. if(wn.ui.form[control_class_name]) {
  4. return new wn.ui.form[control_class_name](opts);
  5. } else {
  6. console.log("Invalid Control Name: " + opts.df.fieldtype);
  7. }
  8. }
  9. // old style
  10. function make_field(docfield, doctype, parent, frm, in_grid, hide_label) { // Factory
  11. return new wn.ui.form.make_control({
  12. df: docfield,
  13. doctype: doctype,
  14. parent: parent,
  15. hide_label: hide_label,
  16. frm: frm
  17. });
  18. }
  19. wn.ui.form.Control = Class.extend({
  20. init: function(opts) {
  21. $.extend(this, opts);
  22. this.make();
  23. },
  24. make: function() {
  25. this.$wrapper = $("<div>").appendTo(this.parent);
  26. this.wrapper = this.$wrapper.get(0);
  27. },
  28. // returns "Read", "Write" or "None"
  29. // as strings based on permissions
  30. get_status: function(explain) {
  31. if(!this.doctype)
  32. return "Write";
  33. return wn.perm.get_field_display_status(this.df,
  34. locals[this.doctype][this.docname], this.perm, explain);
  35. },
  36. refresh: function() {
  37. this.disp_status = this.get_status();
  38. this.$wrapper
  39. && this.$wrapper.toggle(this.disp_status!="None")
  40. && this.$wrapper.trigger("refresh");
  41. },
  42. get_doc: function() {
  43. return this.doctype && this.docname
  44. && locals[this.doctype] && locals[this.doctype][this.docname] || {};
  45. },
  46. parse_validate_and_set_in_model: function(value) {
  47. var me = this;
  48. this.inside_change_event = true;
  49. if(this.parse) value = this.parse(value);
  50. var set = function(value) {
  51. me.set_model_value(value);
  52. me.inside_change_event = false;
  53. me.set_mandatory && me.set_mandatory(value);
  54. }
  55. this.validate ? this.validate(value, set) : set(value);
  56. },
  57. get_parsed_value: function() {
  58. return this.get_value ?
  59. (this.parse ? this.parse(this.get_value()) : this.get_value()) :
  60. undefined;
  61. },
  62. set_model_value: function(value) {
  63. if(wn.model.set_value(this.doctype, this.docname, this.df.fieldname, value)) {
  64. this.frm && this.frm.dirty();
  65. this.last_value = value;
  66. }
  67. },
  68. });
  69. wn.ui.form.ControlHTML = wn.ui.form.Control.extend({
  70. make: function() {
  71. this._super();
  72. var me = this;
  73. this.disp_area = this.wrapper;
  74. this.$wrapper.on("refresh", function() {
  75. if(me.df.options)
  76. me.$wrapper.html(me.df.options);
  77. })
  78. }
  79. });
  80. wn.ui.form.ControlImage = wn.ui.form.Control.extend({
  81. make: function() {
  82. this._super();
  83. var me = this;
  84. this.$wrapper.on("refresh", function() {
  85. me.$wrapper.empty();
  86. if(me.df.options && me.frm.doc[me.df.options]) {
  87. $("<img src='"+me.frm.doc[me.df.options]+"' style='max-width: 70%;'>")
  88. .appendTo(me.$wrapper);
  89. } else {
  90. $("<div class='missing-image'><i class='icon-camera'></i></div>")
  91. .appendTo(me.$wrapper)
  92. }
  93. return false;
  94. })
  95. }
  96. });
  97. wn.ui.form.ControlReadOnly = wn.ui.form.Control.extend({
  98. make: function() {
  99. this._super();
  100. var me = this;
  101. this.$wrapper.on("refresh", function() {
  102. var value = wn.model.get_value(me.doctype, me.docname, me.fieldname);
  103. me.$wrapper.html(value);
  104. })
  105. }
  106. });
  107. wn.ui.form.ControlInput = wn.ui.form.Control.extend({
  108. make: function() {
  109. // parent element
  110. this.make_wrapper();
  111. this.wrapper = this.$wrapper.get(0);
  112. this.wrapper.fieldobj = this; // reference for event handlers
  113. this.set_input_areas();
  114. // set description
  115. this.set_max_width();
  116. this.setup_update_on_refresh();
  117. },
  118. make_wrapper: function() {
  119. if(this.only_input) {
  120. this.$wrapper = $("<span>").appendTo(this.parent);
  121. } else {
  122. this.$wrapper = $('<div class="control-group">\
  123. <label class="control-label"></label>\
  124. <div class="control-input"></div>\
  125. <div class="control-value like-disabled-input" style="display: none;"></div>\
  126. <p class="help-box small text-muted">&nbsp;</p>\
  127. </div>\
  128. </div>').appendTo(this.parent);
  129. }
  130. },
  131. set_input_areas: function() {
  132. if(this.only_input) {
  133. this.input_area = this.wrapper;
  134. } else {
  135. this.label_area = this.label_span = this.$wrapper.find("label").get(0);
  136. this.input_area = this.$wrapper.find(".control-input").get(0);
  137. this.disp_area = this.$wrapper.find(".control-value").get(0);
  138. }
  139. },
  140. set_max_width: function() {
  141. if(['Code', 'Text Editor', 'Text', 'Small Text', 'Table', 'HTML']
  142. .indexOf(this.df.fieldtype)==-1) {
  143. this.$wrapper.css({"max-width": "600px"});
  144. }
  145. },
  146. // update input value, label, description
  147. // display (show/hide/read-only),
  148. // mandatory style on refresh
  149. setup_update_on_refresh: function() {
  150. var me = this;
  151. this.$wrapper.on("refresh", function() {
  152. if(me.disp_status != "None") {
  153. // refresh value
  154. if(me.doctype && me.docname) {
  155. me.value = wn.model.get_value(me.doctype, me.docname, me.df.fieldname);
  156. }
  157. if(me.disp_status=="Write") {
  158. me.disp_area && $(me.disp_area).toggle(false);
  159. $(me.input_area).toggle(true);
  160. !me.has_input && me.make_input();
  161. if(me.doctype && me.docname)
  162. me.set_input(me.value);
  163. } else {
  164. $(me.input_area).toggle(false);
  165. me.disp_area && $(me.disp_area)
  166. .toggle(true)
  167. .html(
  168. wn.format(me.value, me.df, null, locals[me.doctype][me.name])
  169. );
  170. }
  171. me.set_description();
  172. me.set_label();
  173. me.set_mandatory(me.value);
  174. return false;
  175. }
  176. })
  177. },
  178. set_label: function() {
  179. if(this.only_input || this.df.label==this._label)
  180. return;
  181. this.label_span.innerHTML = wn._(this.df.label);
  182. this._label = this.df.label;
  183. },
  184. set_description: function() {
  185. if(this.only_input || this.df.description===this._description)
  186. return;
  187. if(this.df.description) {
  188. this.$wrapper.find(".help-box").html(this.df.description);
  189. } else {
  190. this.set_empty_description();
  191. }
  192. this._description = this.df.description;
  193. },
  194. set_empty_description: function() {
  195. this.$wrapper.find(".help-box").html("&nbsp;");
  196. },
  197. set_mandatory: function(value) {
  198. this.$wrapper.toggleClass("has-error", (this.df.reqd
  199. && (value==null || value==="")) ? true : false);
  200. },
  201. });
  202. wn.ui.form.ControlData = wn.ui.form.ControlInput.extend({
  203. html_element: "input",
  204. input_type: "text",
  205. make_input: function() {
  206. this.$input = $("<"+ this.html_element +">")
  207. .attr("type", this.input_type)
  208. .addClass("col col-lg-12 input-with-feedback")
  209. .prependTo(this.input_area)
  210. this.set_input_attributes();
  211. this.input = this.$input.get(0);
  212. this.has_input = true;
  213. this.bind_change_event();
  214. },
  215. set_input_attributes: function() {
  216. this.$input
  217. .attr("data-fieldtype", this.df.fieldtype)
  218. .attr("data-fieldname", this.df.fieldname)
  219. .attr("placeholder", this.df.placeholder || "")
  220. if(this.doctype)
  221. this.$input.attr("data-doctype", this.doctype);
  222. },
  223. bind_change_event: function() {
  224. var me = this;
  225. this.$input.on("change", this.change || function() {
  226. me.doctype && me.docname && me.get_value
  227. && me.parse_validate_and_set_in_model(me.get_value()); } );
  228. },
  229. set_input: function(val) {
  230. this.$input.val(this.format_for_input(val));
  231. this.last_value = val;
  232. this.set_mandatory && this.set_mandatory(val);
  233. },
  234. get_value: function() {
  235. return this.$input ? this.$input.val() : undefined;
  236. },
  237. format_for_input: function(val) {
  238. return val==null ? "" : val;
  239. },
  240. validate: function(v, callback) {
  241. if(this.df.options == 'Phone') {
  242. if(v+''=='')return '';
  243. v1 = ''
  244. // phone may start with + and must only have numbers later, '-' and ' ' are stripped
  245. v = v.replace(/ /g, '').replace(/-/g, '').replace(/\(/g, '').replace(/\)/g, '');
  246. // allow initial +,0,00
  247. if(v && v.substr(0,1)=='+') {
  248. v1 = '+'; v = v.substr(1);
  249. }
  250. if(v && v.substr(0,2)=='00') {
  251. v1 += '00'; v = v.substr(2);
  252. }
  253. if(v && v.substr(0,1)=='0') {
  254. v1 += '0'; v = v.substr(1);
  255. }
  256. v1 += cint(v) + '';
  257. callback(v1);
  258. } else if(this.df.options == 'Email') {
  259. if(v+''=='')return '';
  260. if(!validate_email(v)) {
  261. msgprint(wn._("Invalid Email") + ": " + v);
  262. callback("");
  263. } else
  264. callback(v);
  265. } else {
  266. callback(v);
  267. }
  268. }
  269. });
  270. wn.ui.form.ControlPassword = wn.ui.form.ControlData.extend({
  271. input_type: "password"
  272. });
  273. wn.ui.form.ControlInt = wn.ui.form.ControlData.extend({
  274. make_input: function() {
  275. this._super();
  276. this.$input.css({"text-align": "right"})
  277. },
  278. validate: function(value, callback) {
  279. return callback(cint(value, null));
  280. }
  281. });
  282. wn.ui.form.ControlFloat = wn.ui.form.ControlInt.extend({
  283. validate: function(value, callback) {
  284. return callback(isNaN(parseFloat(value)) ? null : flt(value));
  285. },
  286. format_for_input: function(value) {
  287. value = format_number(parseFloat(value),
  288. null, cint(wn.boot.sysdefaults.float_precision, null));
  289. return isNaN(value) ? "" : value;
  290. }
  291. });
  292. wn.ui.form.ControlCurrency = wn.ui.form.ControlFloat.extend({
  293. format_for_input: function(value) {
  294. value = format_number(parseFloat(value),
  295. get_number_format(this.get_currency()));
  296. return isNaN(value) ? "" : value;
  297. },
  298. get_currency: function() {
  299. return wn.meta.get_field_currency(this.df, this.get_doc());
  300. }
  301. });
  302. wn.ui.form.ControlPercent = wn.ui.form.ControlFloat;
  303. wn.ui.form.ControlDate = wn.ui.form.ControlData.extend({
  304. datepicker_options: {
  305. altFormat:'yy-mm-dd',
  306. changeYear: true,
  307. yearRange: "-70Y:+10Y",
  308. },
  309. make_input: function() {
  310. this._super();
  311. this.set_datepicker();
  312. },
  313. set_datepicker: function() {
  314. this.datepicker_options.dateFormat =
  315. (wn.boot.sysdefaults.date_format || 'yy-mm-dd').replace('yyyy','yy')
  316. this.$input.datepicker(this.datepicker_options);
  317. },
  318. parse: function(value) {
  319. return value ? dateutil.user_to_str(value) : value;
  320. },
  321. format_for_input: function(value) {
  322. return dateutil.str_to_user(value);
  323. },
  324. validate: function(value, callback) {
  325. var value = wn.datetime.validate(value);
  326. if(!value) {
  327. msgprint (wn._("Date must be in format") + ": " + (sys_defaults.date_format || "yyyy-mm-dd"));
  328. callback("");
  329. }
  330. return callback(value);
  331. }
  332. })
  333. import_timepicker = function() {
  334. wn.require("lib/js/lib/jquery/jquery.ui.slider.min.js");
  335. wn.require("lib/js/lib/jquery/jquery.ui.sliderAccess.js");
  336. wn.require("lib/js/lib/jquery/jquery.ui.timepicker-addon.css");
  337. wn.require("lib/js/lib/jquery/jquery.ui.timepicker-addon.js");
  338. }
  339. wn.ui.form.ControlTime = wn.ui.form.ControlData.extend({
  340. make_input: function() {
  341. import_timepicker();
  342. this._super();
  343. this.$input.timepicker({
  344. timeFormat: 'hh:mm:ss',
  345. });
  346. }
  347. });
  348. wn.ui.form.ControlDatetime = wn.ui.form.ControlDate.extend({
  349. set_datepicker: function() {
  350. this.datepicker_options.dateFormat =
  351. (wn.boot.sysdefaults.date_format || 'yy-mm-dd').replace('yyyy','yy')
  352. this.datepicker_options.timeFormat = "hh:mm:ss";
  353. this.$input.datetimepicker(this.datepicker_options);
  354. },
  355. make_input: function() {
  356. import_timepicker();
  357. this._super();
  358. },
  359. });
  360. wn.ui.form.ControlText = wn.ui.form.ControlData.extend({
  361. html_element: "textarea"
  362. });
  363. wn.ui.form.ControlLongText = wn.ui.form.ControlText;
  364. wn.ui.form.ControlSmallText = wn.ui.form.ControlText;
  365. wn.ui.form.ControlCheck = wn.ui.form.ControlData.extend({
  366. input_type: "checkbox",
  367. make_wrapper: function() {
  368. this.$wrapper = $('<div class="checkbox">\
  369. <label class="input-area">\
  370. <span class="disp-area" style="display:none;"></span>\
  371. <span class="label-area"></span>\
  372. </label>\
  373. </div>').appendTo(this.parent)
  374. },
  375. set_input_areas: function() {
  376. this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0);
  377. this.input_area = this.$wrapper.find(".input-area").get(0);
  378. this.disp_area = this.$wrapper.find(".disp-area").get(0);
  379. },
  380. make_input: function() {
  381. this._super();
  382. this.$input.removeClass("col col-lg-12");
  383. },
  384. parse: function(value) {
  385. return this.input.checked ? 1 : 0;
  386. },
  387. validate: function(value, callback) {
  388. return callback(cint(value));
  389. },
  390. set_input: function(value) {
  391. this.input.checked = value ? 1 : 0;
  392. this.last_value = value;
  393. }
  394. });
  395. wn.ui.form.ControlButton = wn.ui.form.ControlData.extend({
  396. make_input: function() {
  397. var me = this;
  398. this.$input = $('<button class="btn btn-default">')
  399. .prependTo(me.input_area)
  400. .on("click", function() {
  401. if(me.frm && me.frm.cscript) {
  402. if(me.frm.cscript[me.df.fieldname]) {
  403. me.frm.script_manager.trigger(me.df.fieldname, me.doctype, me.docname);
  404. } else {
  405. me.frm.runscript(me.df.options, me);
  406. }
  407. }
  408. });
  409. this.input = this.$input.get(0);
  410. this.has_input = true;
  411. },
  412. set_input_areas: function() {
  413. this._super();
  414. $(this.disp_area).removeClass();
  415. },
  416. set_empty_description: function() {
  417. this.$wrapper.find(".help-box").empty().toggle(false);
  418. },
  419. set_label: function() {
  420. this.$input && this.$input.html(this.df.label);
  421. }
  422. });
  423. wn.ui.form.ControlSelect = wn.ui.form.ControlData.extend({
  424. html_element: "select",
  425. make_input: function() {
  426. var me = this;
  427. this._super();
  428. if(this.df.options=="attach_files:") {
  429. this.setup_attachment();
  430. }
  431. this.set_options();
  432. },
  433. set_input: function(value) {
  434. // refresh options first - (new ones??)
  435. this.set_options();
  436. this.$input.val(value);
  437. // not a possible option, repair
  438. if(this.doctype && this.docname) {
  439. // model value is not an option,
  440. // set the default option (displayed)
  441. var input_value = this.$input.val();
  442. var model_value = wn.model.get_value(this.doctype, this.docname, this.df.fieldname);
  443. if(input_value != (model_value || "")) {
  444. this.set_model_value(input_value);
  445. } else {
  446. this.last_value = value;
  447. }
  448. }
  449. },
  450. setup_attachment: function() {
  451. var me = this;
  452. $(this.input).css({"width": "70%"});
  453. $("<button class='btn btn-default' title='"+ wn._("Add attachment") + "'\
  454. style='margin-bottom: 9px; \
  455. padding-left: 6px; padding-right: 6px; margin-left: 6px;'>\
  456. <i class='icon-plus'></i></button>")
  457. .click(function() {
  458. me.frm.attachments.new_attachment();
  459. })
  460. .appendTo(this.input_area);
  461. $(document).on("upload_complete", function(event, filename, file_url) {
  462. if(cur_frm === me.frm) {
  463. me.set_options();
  464. me.set_input(filename ? ("files/" + filename) : file_url);
  465. }
  466. })
  467. },
  468. set_options: function() {
  469. var options = this.df.options || [];
  470. if(this.df.options=="attach_files:") {
  471. options = this.get_file_attachment_list();
  472. } else if(typeof this.df.options==="string") {
  473. options = this.df.options.split("\n");
  474. }
  475. if(this.in_filter && options[0] != "") {
  476. options = add_lists([''], options);
  477. }
  478. this.$input.empty().add_options(options || []);
  479. },
  480. get_file_attachment_list: function() {
  481. if(!this.frm) return;
  482. var fl = wn.model.docinfo[this.frm.doctype][this.frm.docname];
  483. if(fl) {
  484. this.set_description("");
  485. var options = [""];
  486. for(var fname in fl) {
  487. if(fname.substr(0,4)!="http")
  488. fname = "files/" + fname;
  489. options.push(fname);
  490. }
  491. return options;
  492. } else {
  493. this.set_description(wn._("Please attach a file first."))
  494. return [""];
  495. }
  496. }
  497. });
  498. // special features for link
  499. // buttons
  500. // autocomplete
  501. // link validation
  502. // custom queries
  503. // add_fetches
  504. wn.ui.form.ControlLink = wn.ui.form.ControlData.extend({
  505. make_input: function() {
  506. $('<div class="input-group link-field">\
  507. <input type="text" class="input-with-feedback">\
  508. <div class="input-group-btn">\
  509. <button class="btn btn-default btn-search" title="Search Link">\
  510. <i class="icon-search"></i>\
  511. </button>\
  512. <button class="btn btn-default btn-open" title="Open Link">\
  513. <i class="icon-play"></i>\
  514. </button><button class="btn btn-default btn-new" title="Make New">\
  515. <i class="icon-plus"></i>\
  516. </button>\
  517. </div>\
  518. </div>').appendTo(this.input_area);
  519. this.$input_area = $(this.input_area);
  520. this.$input = this.$input_area.find('input');
  521. this.set_input_attributes();
  522. this.input = this.$input.get(0);
  523. this.has_input = true;
  524. //this.bind_change_event();
  525. var me = this;
  526. this.$input.on("blur", function() {
  527. if(me.doctype && me.docname && !me.autocomplete_open) {
  528. var value = me.get_value();
  529. if(value!==me.last_value) {
  530. me.parse_validate_and_set_in_model(value);
  531. }
  532. }});
  533. this.setup_buttons();
  534. this.setup_autocomplete();
  535. },
  536. setup_buttons: function() {
  537. var me = this;
  538. // magnifier - search
  539. this.$input_area.find(".btn-search").on("click", function() {
  540. new wn.ui.form.LinkSelector({
  541. doctype: me.df.options,
  542. target: me,
  543. txt: me.$input.val()
  544. });
  545. });
  546. // open
  547. if(wn.model.can_read(me.df.options)) {
  548. this.$input_area.find(".btn-open").on("click", function() {
  549. var value = me.get_value();
  550. if(value && me.df.options) wn.set_route("Form", me.df.options, value);
  551. });
  552. } else {
  553. this.$input_area.find(".btn-open").remove();
  554. }
  555. // new
  556. if(wn.model.can_create(me.df.options)) {
  557. this.$input_area.find(".btn-new").on("click", function() {
  558. new_doc(me.df.options)
  559. });
  560. } else {
  561. this.$input_area.find(".btn-new").remove();
  562. }
  563. },
  564. setup_autocomplete: function() {
  565. var me = this;
  566. this.$input.autocomplete({
  567. source: function(request, response) {
  568. var args = {
  569. 'txt': request.term,
  570. 'dt': me.df.options,
  571. };
  572. me.set_custom_query(args);
  573. wn.call({
  574. type: "GET",
  575. method:'webnotes.widgets.search.search_link',
  576. args: args,
  577. callback: function(r) {
  578. response(r.results);
  579. },
  580. });
  581. },
  582. open: function(event, ui) {
  583. if(cur_dialog) {
  584. var zindex = cint(cur_dialog.$wrapper.css("z-index")) + 1
  585. $(this).autocomplete("widget").css("z-index", zindex);
  586. }
  587. me.autocomplete_open = true;
  588. },
  589. close: function(event, ui) {
  590. me.autocomplete_open = false;
  591. },
  592. select: function(event, ui) {
  593. me.set_model_value(ui.item.value);
  594. }
  595. }).data('uiAutocomplete')._renderItem = function(ul, item) {
  596. return $('<li></li>')
  597. .data('item.autocomplete', item)
  598. .append(repl('<a><span style="font-weight: bold;">%(label)s</span><br>\
  599. <span style="font-size:10px;">%(info)s</span></a>',
  600. item))
  601. .appendTo(ul);
  602. };
  603. // remove accessibility span (for now)
  604. this.$wrapper.find(".ui-helper-hidden-accessible").remove();
  605. },
  606. set_custom_query: function(args) {
  607. if(this.get_query) {
  608. var q = this.get_query(this.frm && this.frm.doc, this.doctype, this.docname);
  609. if (typeof(q)==="string") {
  610. args.query = q;
  611. } else if($.isPlainObject(q)) {
  612. if(q.filters) {
  613. $.each(q.filters, function(key, value) {
  614. q.filters[key] = value===undefined ? null : value;
  615. });
  616. }
  617. $.extend(args, q);
  618. }
  619. }
  620. },
  621. validate: function(value, callback) {
  622. // validate the value just entered
  623. var me = this;
  624. if(this.df.options=="[Select]") {
  625. callback(value);
  626. return;
  627. }
  628. var fetch = '';
  629. if(me.frm.fetch_dict[me.df.fieldname])
  630. fetch = me.frm.fetch_dict[me.df.fieldname].columns.join(', ');
  631. wn.call({
  632. method:'webnotes.widgets.form.utils.validate_link',
  633. type: "GET",
  634. args: {
  635. 'value': value,
  636. 'options':me.df.options,
  637. 'fetch': fetch
  638. },
  639. callback: function(r) {
  640. if(r.message=='Ok') {
  641. callback(value);
  642. if(r.fetch_values)
  643. me.set_fetch_values(r.fetch_values);
  644. } else {
  645. callback("")
  646. }
  647. }
  648. });
  649. },
  650. set_fetch_values: function() {
  651. var fl = this.frm.fetch_dict[this.df.fieldname].fields;
  652. for(var i=0; i< fl.length; i++) {
  653. wn.model.set_value(this.doctype, this.docname. fl[i], fetch_values[i]);
  654. }
  655. }
  656. });
  657. wn.ui.form.ControlCode = wn.ui.form.ControlInput.extend({
  658. editor_name: "ACE",
  659. make_input: function() {
  660. $(this.input_area).css({"min-height":"360px"});
  661. var me = this;
  662. this.editor = new wn.editors[this.editor_name]({
  663. parent: this.input_area,
  664. change: function(value) {
  665. me.parse_validate_and_set_in_model(value);
  666. },
  667. field: this
  668. });
  669. this.has_input = true;
  670. },
  671. get_value: function() {
  672. return this.editor.get_value();
  673. },
  674. set_input: function(value) {
  675. this.editor.set_input(value);
  676. this.last_value = value;
  677. }
  678. });
  679. wn.ui.form.ControlTextEditor = wn.ui.form.ControlCode.extend({
  680. editor_name: "BootstrapWYSIWYG"
  681. });
  682. wn.ui.form.ControlTable = wn.ui.form.Control.extend({
  683. make: function() {
  684. this._super();
  685. this.grid = new wn.ui.form.Grid({
  686. frm: this.frm,
  687. df: this.df,
  688. perm: this.perm,
  689. parent: this.wrapper
  690. })
  691. if(this.frm)
  692. this.frm.grids[this.frm.grids.length] = this;
  693. // description
  694. if(this.df.description) {
  695. $('<p class="text-muted small">' + this.df.description + '</p>')
  696. .appendTo(this.wrapper);
  697. }
  698. var me = this;
  699. this.$wrapper.on("refresh", function() {
  700. me.grid.refresh();
  701. return false;
  702. });
  703. }
  704. })