No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

521 líneas
13 KiB

  1. // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. // MIT License. See license.txt
  3. // new re-re-factored Listing object
  4. // now called BaseList
  5. //
  6. // opts:
  7. // parent
  8. // method (method to call on server)
  9. // args (additional args to method)
  10. // get_args (method to return args as dict)
  11. // show_filters [false]
  12. // doctype
  13. // filter_fields (if given, this list is rendered, else built from doctype)
  14. // query or get_query (will be deprecated)
  15. // query_max
  16. // buttons_in_frame
  17. // no_result_message ("No result")
  18. // page_length (20)
  19. // hide_refresh (False)
  20. // no_toolbar
  21. // new_doctype
  22. // [function] render_row(parent, data)
  23. // [function] onrun
  24. // no_loading (no ajax indicator)
  25. frappe.provide('frappe.ui');
  26. frappe.ui.BaseList = Class.extend({
  27. init: function (opts) {
  28. this.opts = opts || {};
  29. this.set_defaults();
  30. if (opts) {
  31. this.make();
  32. }
  33. },
  34. set_defaults: function () {
  35. this.page_length = 20;
  36. this.start = 0;
  37. this.data = [];
  38. },
  39. make: function (opts) {
  40. if (opts) {
  41. this.opts = opts;
  42. }
  43. this.prepare_opts();
  44. $.extend(this, this.opts);
  45. // make dom
  46. this.wrapper = $(frappe.render_template('listing', this.opts));
  47. this.parent.append(this.wrapper);
  48. this.set_events();
  49. if (this.page) {
  50. this.wrapper.find('.list-toolbar-wrapper').hide();
  51. }
  52. if (this.show_filters) {
  53. this.make_filters();
  54. }
  55. },
  56. prepare_opts: function () {
  57. if (this.opts.new_doctype) {
  58. if (!frappe.boot.user.can_create.includes(this.opts.new_doctype)) {
  59. this.opts.new_doctype = null;
  60. }
  61. }
  62. if (!this.opts.no_result_message) {
  63. this.opts.no_result_message = __('Nothing to show');
  64. }
  65. if (!this.opts.page_length) {
  66. this.opts.page_length = this.user_settings && this.user_settings.limit || 20;
  67. }
  68. this.opts._more = __('More');
  69. },
  70. add_button: function (label, click, icon) {
  71. if (this.page) {
  72. return this.page.add_menu_item(label, click, icon)
  73. } else {
  74. this.wrapper.find('.list-toolbar-wrapper').removeClass('hide');
  75. return $('<button class="btn btn-default"></button>')
  76. .appendTo(this.wrapper.find('.list-toolbar'))
  77. .html((icon ? ('<i class="' + icon + '"></i> ') : '') + label)
  78. .click(click);
  79. }
  80. },
  81. set_events: function () {
  82. var me = this;
  83. // next page
  84. this.wrapper.find('.btn-more').click(function () {
  85. me.run(true);
  86. });
  87. this.wrapper.find(".btn-group-paging").on('click', '.btn', function () {
  88. me.page_length = cint($(this).attr("data-value"));
  89. me.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
  90. $(this).addClass("btn-info");
  91. // always reset when changing list page length
  92. me.run();
  93. });
  94. // select the correct page length
  95. if (this.opts.page_length !== 20) {
  96. this.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
  97. this.wrapper
  98. .find(".btn-group-paging .btn[data-value='" + this.opts.page_length + "']")
  99. .addClass('btn-info');
  100. }
  101. // title
  102. if (this.title) {
  103. this.wrapper.find('h3').html(this.title).show();
  104. }
  105. // new
  106. this.set_primary_action();
  107. if (me.no_toolbar || me.hide_toolbar) {
  108. me.wrapper.find('.list-toolbar-wrapper').hide();
  109. }
  110. },
  111. set_primary_action: function () {
  112. var me = this;
  113. if (this.new_doctype) {
  114. this.page.set_primary_action(
  115. __("New"),
  116. me.make_new_doc.bind(me, me.new_doctype),
  117. "octicon octicon-plus"
  118. );
  119. } else {
  120. this.page.clear_primary_action();
  121. }
  122. },
  123. make_new_doc: function (doctype) {
  124. var me = this;
  125. frappe.model.with_doctype(doctype, function () {
  126. if (me.custom_new_doc) {
  127. me.custom_new_doc(doctype);
  128. } else {
  129. if (me.filter_list) {
  130. frappe.route_options = {};
  131. me.filter_list.get_filters().forEach(function (f, i) {
  132. if (f[2] === "=" && !frappe.model.std_fields_list.includes(f[1])) {
  133. frappe.route_options[f[1]] = f[3];
  134. }
  135. });
  136. }
  137. frappe.new_doc(doctype, true);
  138. }
  139. });
  140. },
  141. make_filters: function () {
  142. this.make_standard_filters();
  143. this.filter_list = new frappe.ui.FilterList({
  144. base_list: this,
  145. parent: this.wrapper.find('.list-filters').show(),
  146. doctype: this.doctype,
  147. filter_fields: this.filter_fields,
  148. default_filters: this.default_filters || []
  149. });
  150. // default filter for submittable doctype
  151. if (frappe.model.is_submittable(this.doctype)) {
  152. this.filter_list.add_filter(this.doctype, "docstatus", "!=", 2);
  153. }
  154. },
  155. make_standard_filters: function() {
  156. var me = this;
  157. if (this.standard_filters_added) {
  158. return;
  159. }
  160. if (this.meta) {
  161. this.page.add_field({
  162. fieldtype: 'Data',
  163. label: 'ID',
  164. condition: 'like',
  165. fieldname: 'name',
  166. onchange: () => { me.refresh(true); }
  167. });
  168. this.meta.fields.forEach(function(df) {
  169. if(df.in_standard_filter && !frappe.model.no_value_type.includes(df.fieldtype)) {
  170. let options = df.options;
  171. let condition = '=';
  172. let fieldtype = df.fieldtype;
  173. if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) {
  174. fieldtype = 'Data',
  175. condition = 'like'
  176. }
  177. if(df.fieldtype == "Select" && df.options) {
  178. options = df.options.split("\n");
  179. if(options.length > 0 && options[0] != "") {
  180. options.unshift("");
  181. options = options.join("\n");
  182. }
  183. }
  184. me.page.add_field({
  185. fieldtype: fieldtype,
  186. label: __(df.label),
  187. options: options,
  188. fieldname: df.fieldname,
  189. condition: condition,
  190. onchange: () => {me.refresh(true);}
  191. });
  192. }
  193. });
  194. }
  195. this.standard_filters_added = true;
  196. },
  197. update_standard_filters: function(filters) {
  198. let me = this;
  199. for(let key in this.page.fields_dict) {
  200. let field = this.page.fields_dict[key];
  201. let value = field.get_value();
  202. if (value) {
  203. if (field.df.condition==='like' && !value.includes('%')) {
  204. value = '%' + value + '%';
  205. }
  206. filters.push([
  207. me.doctype,
  208. field.df.fieldname,
  209. field.df.condition || '=',
  210. value
  211. ]);
  212. }
  213. }
  214. },
  215. clear: function () {
  216. this.data = [];
  217. this.wrapper.find('.result-list').empty();
  218. this.wrapper.find('.result').show();
  219. this.wrapper.find('.no-result').hide();
  220. this.start = 0;
  221. this.onreset && this.onreset();
  222. },
  223. set_filters_from_route_options: function ({clear_filters=true} = {}) {
  224. var me = this;
  225. if(this.filter_list && clear_filters) {
  226. this.filter_list.clear_filters();
  227. }
  228. for(var field in frappe.route_options) {
  229. var value = frappe.route_options[field];
  230. var doctype = null;
  231. // if `Child DocType.fieldname`
  232. if (field.includes(".")) {
  233. doctype = field.split(".")[0];
  234. field = field.split(".")[1];
  235. }
  236. // find the table in which the key exists
  237. // for example the filter could be {"item_code": "X"}
  238. // where item_code is in the child table.
  239. // we can search all tables for mapping the doctype
  240. if (!doctype) {
  241. doctype = frappe.meta.get_doctype_for_field(me.doctype, field);
  242. }
  243. if (doctype && me.filter_list) {
  244. if ($.isArray(value)) {
  245. me.filter_list.add_filter(doctype, field, value[0], value[1]);
  246. } else {
  247. me.filter_list.add_filter(doctype, field, "=", value);
  248. }
  249. }
  250. }
  251. frappe.route_options = null;
  252. },
  253. run: function(more) {
  254. setTimeout(() => this._run(more), 100);
  255. },
  256. _run: function (more) {
  257. var me = this;
  258. if (!more) {
  259. this.start = 0;
  260. this.onreset && this.onreset();
  261. }
  262. var args = this.get_call_args();
  263. this.save_user_settings_locally(args);
  264. // user_settings are saved by db_query.py when dirty
  265. $.extend(args, {
  266. user_settings: frappe.model.user_settings[this.doctype]
  267. });
  268. return frappe.call({
  269. method: this.opts.method || 'frappe.desk.query_builder.runquery',
  270. type: "GET",
  271. freeze: this.opts.freeze !== undefined ? this.opts.freeze : true,
  272. args: args,
  273. callback: function (r) {
  274. me.dirty = false;
  275. me.render_results(r);
  276. },
  277. no_spinner: this.opts.no_loading
  278. });
  279. },
  280. save_user_settings_locally: function (args) {
  281. if (this.opts.save_user_settings && this.doctype && !this.docname) {
  282. // save list settings locally
  283. var user_settings = frappe.model.user_settings[this.doctype];
  284. var different = false;
  285. if (!user_settings) {
  286. return;
  287. }
  288. if (!frappe.utils.arrays_equal(args.filters, user_settings.filters)) {
  289. // settings are dirty if filters change
  290. user_settings.filters = args.filters;
  291. different = true;
  292. }
  293. if (user_settings.order_by !== args.order_by) {
  294. user_settings.order_by = args.order_by;
  295. different = true;
  296. }
  297. if (user_settings.limit !== args.limit_page_length) {
  298. user_settings.limit = args.limit_page_length || 20
  299. different = true;
  300. }
  301. // save fields in list settings
  302. if (args.save_user_settings_fields) {
  303. user_settings.fields = args.fields;
  304. }
  305. if (different) {
  306. user_settings.updated_on = moment().toString();
  307. }
  308. }
  309. },
  310. get_call_args: function () {
  311. // load query
  312. if (!this.method) {
  313. var query = this.get_query && this.get_query() || this.query;
  314. query = this.add_limits(query);
  315. var args = {
  316. query_max: this.query_max,
  317. as_dict: 1
  318. }
  319. args.simple_query = query;
  320. } else {
  321. var args = {
  322. start: this.start,
  323. page_length: this.page_length
  324. }
  325. }
  326. // append user-defined arguments
  327. if (this.args)
  328. $.extend(args, this.args)
  329. if (this.get_args) {
  330. $.extend(args, this.get_args());
  331. }
  332. return args;
  333. },
  334. render_results: function (r) {
  335. if (this.start === 0)
  336. this.clear();
  337. this.wrapper.find('.btn-more, .list-loading').hide();
  338. var values = [];
  339. if (r.message) {
  340. values = this.get_values_from_response(r.message);
  341. }
  342. var show_results = true;
  343. if(this.show_no_result) {
  344. if($.isFunction(this.show_no_result)) {
  345. show_results = !this.show_no_result()
  346. } else {
  347. show_results = !this.show_no_result;
  348. }
  349. }
  350. // render result view when
  351. // length > 0 OR
  352. // explicitly set by flag
  353. if (values.length || show_results) {
  354. this.data = this.data.concat(values);
  355. this.render_view(values);
  356. this.update_paging(values);
  357. } else if (this.start === 0) {
  358. // show no result message
  359. this.wrapper.find('.result').hide();
  360. var msg = '';
  361. var no_result_message = this.no_result_message;
  362. if(no_result_message && $.isFunction(no_result_message)) {
  363. msg = no_result_message();
  364. } else if(typeof no_result_message === 'string') {
  365. msg = no_result_message;
  366. } else {
  367. msg = __('No Results')
  368. }
  369. this.wrapper.find('.no-result').html(msg).show();
  370. }
  371. this.wrapper.find('.list-paging-area')
  372. .toggle(values.length > 0|| this.start > 0);
  373. // callbacks
  374. if (this.onrun) this.onrun();
  375. if (this.callback) this.callback(r);
  376. this.wrapper.trigger("render-complete");
  377. },
  378. get_values_from_response: function (data) {
  379. // make dictionaries from keys and values
  380. if (data.keys && $.isArray(data.keys)) {
  381. return frappe.utils.dict(data.keys, data.values);
  382. } else {
  383. return data;
  384. }
  385. },
  386. render_view: function (values) {
  387. // override this method in derived class
  388. },
  389. update_paging: function (values) {
  390. if (values.length >= this.page_length) {
  391. this.wrapper.find('.btn-more').show();
  392. this.start += this.page_length;
  393. }
  394. },
  395. refresh: function () {
  396. this.run();
  397. },
  398. add_limits: function (query) {
  399. return query + ' LIMIT ' + this.start + ',' + (this.page_length + 1);
  400. },
  401. set_filter: function (fieldname, label, no_run, no_duplicate) {
  402. var filter = this.filter_list.get_filter(fieldname);
  403. if (filter) {
  404. var value = cstr(filter.field.get_value());
  405. if (value.includes(label)) {
  406. // already set
  407. return false
  408. } else if (no_duplicate) {
  409. filter.set_values(this.doctype, fieldname, "=", label);
  410. } else {
  411. // second filter set for this field
  412. if (fieldname == '_user_tags' || fieldname == "_liked_by") {
  413. // and for tags
  414. this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
  415. } else {
  416. // or for rest using "in"
  417. filter.set_values(this.doctype, fieldname, 'in', value + ', ' + label);
  418. }
  419. }
  420. } else {
  421. // no filter for this item,
  422. // setup one
  423. if (['_user_tags', '_comments', '_assign', '_liked_by'].includes(fieldname)) {
  424. this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
  425. } else {
  426. this.filter_list.add_filter(this.doctype, fieldname, '=', label);
  427. }
  428. }
  429. if (!no_run)
  430. this.run();
  431. },
  432. init_user_settings: function () {
  433. this.user_settings = frappe.model.user_settings[this.doctype] || {};
  434. },
  435. call_for_selected_items: function (method, args) {
  436. var me = this;
  437. args.names = this.get_checked_items().map(function (item) {
  438. return item.name;
  439. });
  440. frappe.call({
  441. method: method,
  442. args: args,
  443. freeze: true,
  444. callback: function (r) {
  445. if (!r.exc) {
  446. if (me.list_header) {
  447. me.list_header.find(".list-select-all").prop("checked", false);
  448. }
  449. me.refresh();
  450. }
  451. }
  452. });
  453. }
  454. });