25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

407 lines
11 KiB

  1. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
  2. // MIT License. See license.txt
  3. wn.views.get_listview = function(doctype, parent) {
  4. var meta = locals.DocType[doctype];
  5. if(wn.doclistviews[doctype]) {
  6. var listview = new wn.doclistviews[doctype](parent);
  7. } else {
  8. var listview = new wn.views.ListView(parent, doctype);
  9. }
  10. return listview;
  11. }
  12. wn.provide("wn.listview_settings");
  13. wn.views.ListView = Class.extend({
  14. init: function(doclistview, doctype) {
  15. this.doclistview = doclistview;
  16. this.doctype = doctype;
  17. this.settings = wn.listview_settings[this.doctype] || {};
  18. this.set_fields();
  19. this.set_columns();
  20. this.id_list = [];
  21. if(this.settings.group_by)
  22. this.group_by = this.settings.group_by;
  23. var me = this;
  24. this.doclistview.onreset = function() {
  25. me.id_list = [];
  26. }
  27. },
  28. set_fields: function() {
  29. var me = this;
  30. var t = "`tab"+this.doctype+"`.";
  31. this.fields = [t + 'name', t + 'owner', t + 'docstatus',
  32. t + '_user_tags', t + 'modified', t + 'modified_by'];
  33. this.stats = ['_user_tags'];
  34. // add workflow field (as priority)
  35. this.workflow_state_fieldname = wn.workflow.get_state_fieldname(this.doctype);
  36. if(this.workflow_state_fieldname) {
  37. this.fields.push(t + this.workflow_state_fieldname);
  38. this.stats.push(this.workflow_state_fieldname);
  39. }
  40. $.each(wn.model.get("DocField", {"parent":this.doctype, "in_list_view":1}), function(i,d) {
  41. if(wn.perm.has_perm(me.doctype, d.permlevel, READ)) {
  42. if(d.fieldtype=="Image" && d.options) {
  43. me.fields.push(t + "`" + d.options + "`");
  44. } else {
  45. me.fields.push(t + "`" + d.fieldname + "`");
  46. }
  47. if(d.fieldtype=="Select") {
  48. me.stats.push(d.fieldname);
  49. }
  50. // currency field for symbol (multi-currency)
  51. if(d.fieldtype=="Currency" && d.options) {
  52. if(d.options.indexOf(":")!=-1) {
  53. me.fields.push(t + "`" + d.options.split(":")[1] + "`");
  54. } else {
  55. me.fields.push(t + "`" + d.options + "`");
  56. };
  57. }
  58. }
  59. });
  60. // additional fields
  61. if(this.settings.add_fields) {
  62. $.each(this.settings.add_fields, function(i, d) {
  63. if(me.fields.indexOf(d)==-1)
  64. me.fields.push(d);
  65. });
  66. }
  67. },
  68. set_columns: function() {
  69. this.columns = [];
  70. var me = this;
  71. this.columns.push({colspan: 1, content:'avatar'});
  72. this.columns.push({colspan: 3, content:'name'});
  73. if(this.workflow_state_fieldname) {
  74. this.columns.push({
  75. colspan: 2,
  76. content: this.workflow_state_fieldname,
  77. type:"select"
  78. });
  79. }
  80. // overridden
  81. var overridden = $.map(this.settings.add_columns || [], function(d) {
  82. return d.content;
  83. });
  84. $.each(wn.model.get("DocField", {"parent":this.doctype, "in_list_view":1}),
  85. function(i,d) {
  86. if(in_list(overridden, d.fieldname)) {
  87. return;
  88. }
  89. // field width
  90. var colspan = "2";
  91. if(in_list(["Int", "Percent"], d.fieldtype)) {
  92. colspan = "1";
  93. } else if(d.fieldtype=="Check") {
  94. colspan = "1";
  95. } else if(in_list(["name", "subject", "title"], d.fieldname)) { // subjects are longer
  96. colspan = "3";
  97. } else if(d.fieldtype=="Text Editor" || d.fieldtype=="Text") {
  98. colspan = "3";
  99. }
  100. me.columns.push({colspan: colspan, content: d.fieldname,
  101. type:d.fieldtype, df:d, title:wn._(d.label) });
  102. });
  103. // additional columns
  104. if(this.settings.add_columns) {
  105. $.each(this.settings.add_columns, function(i, d) {
  106. me.columns.push(d);
  107. });
  108. }
  109. // expand "name" if there are few columns
  110. var total_colspan = 0;
  111. $.each(this.columns, function(i, c) { total_colspan += cint(c.colspan); });
  112. if(total_colspan < 8) {
  113. $.each(this.columns,
  114. function(i, c) { if(c.content==="name") { c.colspan = 4; return false; } });
  115. }
  116. },
  117. render: function(row, data) {
  118. this.prepare_data(data);
  119. $(row).removeClass("list-row");
  120. // maintain id_list to avoid duplication incase
  121. // of filtering by child table
  122. if(in_list(this.id_list, data.name))
  123. return;
  124. else
  125. this.id_list.push(data.name);
  126. var body = $('<div class="doclist-row row">').appendTo(row),
  127. colspans = 0,
  128. me = this;
  129. // make table
  130. $.each(this.columns, function(i, v) {
  131. var colspan = v.colspan || 2;
  132. colspans = colspans + flt(colspan)
  133. if(colspans <= 12) {
  134. var col = me.make_column(body, colspan);
  135. me.render_column(data, col, v);
  136. }
  137. });
  138. // row #2
  139. var row2 = $('<div class="row">\
  140. <div class="col-xs-12">\
  141. <div class="col-xs-3"></div>\
  142. <div class="col-xs-7">\
  143. <div class="list-tag hidden-xs"></div>\
  144. <div class="list-last-modified visible-xs text-muted small"></div>\
  145. </div>\
  146. <div class="col-xs-2 timestamp small text-muted" style="padding-right: 4px;\
  147. margin-top: -3px; text-align: right;">\
  148. </div>\
  149. </div>\
  150. </div>').appendTo(row);
  151. // modified
  152. row2.find(".timestamp").html(comment_when(data.modified));
  153. row2.find(".list-last-modified").html(wn._("Last updated by") + ": " + wn.user_info(data.modified_by).fullname);
  154. // add tags
  155. var tag_editor = new wn.ui.TagEditor({
  156. parent: row2.find(".list-tag"),
  157. frm: {
  158. doctype: this.doctype,
  159. docname: data.name
  160. },
  161. user_tags: data._user_tags
  162. });
  163. tag_editor.$w.on("click", ".tagit-label", function() {
  164. me.doclistview.set_filter("_user_tags",
  165. $(this).text());
  166. });
  167. },
  168. make_column: function(body, colspan) {
  169. var col = $("<div class='col'>")
  170. .appendTo(body)
  171. .addClass("col-xs-" + cint(colspan))
  172. .css({
  173. "white-space": "nowrap",
  174. "text-overflow": "ellipsis",
  175. "max-height": "30px",
  176. })
  177. return col;
  178. },
  179. render_column: function(data, parent, opts) {
  180. var me = this;
  181. if(opts.type) opts.type= opts.type.toLowerCase();
  182. // style
  183. if(opts.css) {
  184. $.each(opts.css, function(k, v) { $(parent).css(k, v)});
  185. }
  186. // multiple content
  187. if(opts.content.indexOf && opts.content.indexOf('+')!=-1) {
  188. $.map(opts.content.split('+'), function(v) {
  189. me.render_column(data, parent, {content:v, title: opts.title});
  190. });
  191. return;
  192. }
  193. // content
  194. if(typeof opts.content=='function') {
  195. opts.content(parent, data, me);
  196. }
  197. else if(opts.content=='name') {
  198. if(wn.model.is_submittable(this.doctype)) {
  199. $(parent).append(repl('<span class="docstatus" style="margin-right: 3px;"> \
  200. <i class="%(docstatus_icon)s" \
  201. title="%(docstatus_title)s"></i></span>', data));
  202. }
  203. $("<a>")
  204. .attr("href", "#Form/" + data.doctype + "/" + encodeURIComponent(data.name))
  205. .html(data.name)
  206. .appendTo(parent.css({"overflow":"hidden"}));
  207. }
  208. else if(opts.content=='avatar' || opts.content=='avatar_modified') {
  209. if(wn.model.can_delete(this.doctype) || this.settings.selectable) {
  210. $('<input class="list-delete" type="checkbox">')
  211. .data('name', data.name)
  212. .data('data', data)
  213. .css({"margin-right": "5px", "margin-left": "-2px"})
  214. .appendTo(parent)
  215. }
  216. var $avatar = $(wn.avatar(data.modified_by, false, wn._("Modified by")+": "
  217. + wn.user_info(data.modified_by).fullname))
  218. .appendTo($(parent).css({"margin-top": "-5px"}))
  219. .css({"max-width": "100%"})
  220. .addClass("hidden-xs-inline-block")
  221. }
  222. else if(opts.content=='check') {
  223. }
  224. else if(opts.type=='bar-graph' || opts.type=="percent") {
  225. this.render_bar_graph(parent, data, opts.content, opts.label);
  226. }
  227. else if(opts.template) {
  228. $(parent).append(repl(opts.template, data));
  229. }
  230. else if(opts.type=="date" && data[opts.content]) {
  231. $("<span>")
  232. .html(wn.datetime.str_to_user(data[opts.content]))
  233. .css({"color":"#888"})
  234. .appendTo(parent);
  235. }
  236. else if(opts.type=="image") {
  237. data[opts.content] = data[opts.df.options];
  238. if(data[opts.content])
  239. $("<img>")
  240. .attr("src", wn.utils.get_file_link(data[opts.content]))
  241. .css({
  242. "max-width": "100%",
  243. "max-height": "30px"
  244. })
  245. .appendTo(parent);
  246. }
  247. else if(opts.type=="select" && data[opts.content]) {
  248. var label_class = "label-default";
  249. var style = wn.utils.guess_style(data[opts.content]);
  250. if(style) label_class = "label-" + style;
  251. $("<span>"
  252. + data[opts.content] + "</span>")
  253. .css({"cursor":"pointer"})
  254. .addClass("label")
  255. .addClass(label_class)
  256. .attr("data-fieldname", opts.content)
  257. .click(function() {
  258. me.doclistview.set_filter($(this).attr("data-fieldname"),
  259. $(this).text());
  260. })
  261. .appendTo(parent.css({"overflow":"hidden"}));
  262. }
  263. else if(data[opts.content]) {
  264. $("<span>")
  265. .html(wn.format(data[opts.content], opts.df, null, data))
  266. .appendTo(parent.css({"overflow":"hidden"}))
  267. }
  268. // finally
  269. if(!$(parent).html()) {
  270. $("<span>-</span>").css({color:"#ccc"}).appendTo(parent);
  271. }
  272. // title
  273. if(!in_list(["avatar", "_user_tags", "check"], opts.content)) {
  274. if($(parent).attr("title")==undefined) {
  275. $(parent).attr("title", (opts.title || opts.content) + ": "
  276. + (data[opts.content] || "Not Set"))
  277. }
  278. $(parent).tooltip();
  279. }
  280. },
  281. show_hide_check_column: function() {
  282. if(!this.doclistview.can_delete) {
  283. this.columns = $.map(this.columns, function(v, i) { if(v.content!='check') return v });
  284. }
  285. },
  286. prepare_data: function(data) {
  287. if(data.modified)
  288. this.prepare_when(data, data.modified);
  289. // docstatus
  290. if(data.docstatus==0 || data.docstatus==null) {
  291. data.docstatus_icon = 'icon-check-empty';
  292. data.docstatus_title = wn._('Editable');
  293. } else if(data.docstatus==1) {
  294. data.docstatus_icon = 'icon-lock';
  295. data.docstatus_title = wn._('Submitted');
  296. } else if(data.docstatus==2) {
  297. data.docstatus_icon = 'icon-remove';
  298. data.docstatus_title = wn._('Cancelled');
  299. }
  300. // nulls as strings
  301. for(key in data) {
  302. if(data[key]==null) {
  303. data[key]='';
  304. }
  305. }
  306. // prepare data in settings
  307. if(this.settings.prepare_data)
  308. this.settings.prepare_data(data);
  309. },
  310. prepare_when: function(data, date_str) {
  311. if (!date_str) date_str = data.modified;
  312. // when
  313. data.when = (dateutil.str_to_user(date_str)).split(' ')[0];
  314. var diff = dateutil.get_diff(dateutil.get_today(), date_str.split(' ')[0]);
  315. if(diff==0) {
  316. data.when = dateutil.comment_when(date_str);
  317. }
  318. if(diff == 1) {
  319. data.when = wn._('Yesterday')
  320. }
  321. if(diff == 2) {
  322. data.when = wn._('2 days ago')
  323. }
  324. },
  325. render_bar_graph: function(parent, data, field, label) {
  326. var args = {
  327. percent: data[field],
  328. label: label
  329. }
  330. $(parent).append(repl('<span class="progress" style="width: 100%; float: left;"> \
  331. <span class="progress-bar" title="%(percent)s% %(label)s" \
  332. style="width: %(percent)s%;"></span>\
  333. </span>', args));
  334. },
  335. render_icon: function(parent, icon_class, label) {
  336. var icon_html = "<i class='%(icon_class)s' title='%(label)s'></i>";
  337. $(parent).append(repl(icon_html, {icon_class: icon_class, label: label || ''}));
  338. }
  339. });
  340. // embeddable
  341. wn.provide('wn.views.RecordListView');
  342. wn.views.RecordListView = wn.views.DocListView.extend({
  343. init: function(doctype, wrapper, ListView) {
  344. this.doctype = doctype;
  345. this.wrapper = wrapper;
  346. this.listview = new ListView(this, doctype);
  347. this.listview.parent = this;
  348. this.setup();
  349. },
  350. setup: function() {
  351. var me = this;
  352. me.page_length = 10;
  353. $(me.wrapper).empty();
  354. me.init_list();
  355. },
  356. get_args: function() {
  357. var args = this._super();
  358. $.each((this.default_filters || []), function(i, f) {
  359. args.filters.push(f);
  360. });
  361. args.docstatus = args.docstatus.concat((this.default_docstatus || []));
  362. return args;
  363. },
  364. });