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.

331 lines
10 KiB

  1. odoo.define('web_timeline.TimelineRenderer', function (require) {
  2. "use strict";
  3. var AbstractRenderer = require('web.AbstractRenderer');
  4. var core = require('web.core');
  5. var time = require('web.time');
  6. var _t = core._t;
  7. var CalendarRenderer = AbstractRenderer.extend({
  8. template: "TimelineView",
  9. events: _.extend({}, AbstractRenderer.prototype.events, {
  10. }),
  11. init: function (parent, state, params) {
  12. this._super.apply(this, arguments);
  13. this.modelName = params.model;
  14. this.mode = params.mode;
  15. this.options = params.options;
  16. this.permissions = params.permissions;
  17. this.timeline = params.timeline;
  18. this.date_start = params.date_start;
  19. this.date_stop = params.date_stop;
  20. this.date_delay = params.date_delay;
  21. this.colors = params.colors;
  22. this.fieldNames = params.fieldNames;
  23. this.view = params.view;
  24. this.modelClass = this.view.model;
  25. },
  26. start: function () {
  27. var self = this;
  28. var attrs = this.arch.attrs;
  29. this.current_window = {
  30. start: new moment(),
  31. end: new moment().add(24, 'hours')
  32. };
  33. this.$el.addClass(attrs.class);
  34. this.$timeline = this.$el.find(".oe_timeline_widget");
  35. if (!this.date_start) {
  36. throw new Error(_t("Timeline view has not defined 'date_start' attribute."));
  37. }
  38. this._super.apply(this, self);
  39. },
  40. _render: function () {
  41. this.add_events();
  42. var self = this;
  43. return $.when().then(function () {
  44. // Prevent Double Rendering on Updates
  45. if (!self.timeline) {
  46. self.init_timeline();
  47. $(window).trigger('resize');
  48. }
  49. });
  50. },
  51. add_events: function() {
  52. var self = this;
  53. this.$(".oe_timeline_button_today").click(function(){
  54. self._onTodayClicked();});
  55. this.$(".oe_timeline_button_scale_day").click(function(){
  56. self._onScaleDayClicked();});
  57. this.$(".oe_timeline_button_scale_week").click(function(){
  58. self._onScaleWeekClicked();});
  59. this.$(".oe_timeline_button_scale_month").click(function(){
  60. self._onScaleMonthClicked();});
  61. this.$(".oe_timeline_button_scale_year").click(function(){
  62. self._onScaleYearClicked();});
  63. },
  64. _onTodayClicked: function () {
  65. this.current_window = {
  66. start: new moment(),
  67. end: new moment().add(24, 'hours')
  68. };
  69. if (this.timeline) {
  70. this.timeline.setWindow(this.current_window);
  71. }
  72. },
  73. _onScaleDayClicked: function () {
  74. this._scaleCurrentWindow(24);
  75. },
  76. _onScaleWeekClicked: function () {
  77. this._scaleCurrentWindow(24 * 7);
  78. },
  79. _onScaleMonthClicked: function () {
  80. this._scaleCurrentWindow(24 * 30);
  81. },
  82. _onScaleYearClicked: function () {
  83. this._scaleCurrentWindow(24 * 365);
  84. },
  85. _scaleCurrentWindow: function (factor) {
  86. if (this.timeline) {
  87. this.current_window = this.timeline.getWindow();
  88. this.current_window.end = moment(this.current_window.start).add(factor, 'hours');
  89. this.timeline.setWindow(this.current_window);
  90. }
  91. },
  92. _onClick: function (e) {
  93. // handle a click on a group header
  94. if (e.what === 'group-label') {
  95. return self._onGroupClick(e);
  96. }
  97. },
  98. _onGroupClick: function (e) {
  99. if (e.group === -1) {
  100. return;
  101. }
  102. return this.do_action({
  103. type: 'ir.actions.act_window',
  104. res_model: this.view.fields_view.fields[this.last_group_bys[0]].relation,
  105. res_id: e.group,
  106. target: 'new',
  107. views: [[false, 'form']]
  108. });
  109. },
  110. _computeMode: function() {
  111. if (this.mode) {
  112. var start = false, end = false;
  113. switch (this.mode) {
  114. case 'day':
  115. start = new moment().startOf('day');
  116. end = new moment().endOf('day');
  117. break;
  118. case 'week':
  119. start = new moment().startOf('week');
  120. end = new moment().endOf('week');
  121. break;
  122. case 'month':
  123. start = new moment().startOf('month');
  124. end = new moment().endOf('month');
  125. break;
  126. }
  127. if (end && start) {
  128. this.options.start = start;
  129. this.options.end = end;
  130. } else {
  131. this.mode = 'fit';
  132. }
  133. }
  134. },
  135. init_timeline: function () {
  136. var self = this;
  137. this._computeMode();
  138. this.options.editable = {
  139. // add new items by double tapping
  140. add: this.modelClass.data.rights.create,
  141. // drag items horizontally
  142. updateTime: this.modelClass.data.rights.write,
  143. // drag items from one group to another
  144. updateGroup: this.modelClass.data.rights.write,
  145. // delete an item by tapping the delete button top right
  146. remove: this.modelClass.data.rights.unlink,
  147. };
  148. $.extend(this.options, {
  149. onAdd: self.on_add,
  150. onMove: self.on_move,
  151. onUpdate: self.on_update,
  152. onRemove: self.on_remove,
  153. });
  154. this.timeline = new vis.Timeline(self.$timeline.empty().get(0));
  155. this.timeline.setOptions(this.options);
  156. if (self.mode && self['on_scale_' + self.mode + '_clicked']) {
  157. self['on_scale_' + self.mode + '_clicked']();
  158. }
  159. this.timeline.on('click', self._onClick);
  160. var group_bys = this.arch.attrs.default_group_by.split(',');
  161. this.last_group_bys = group_bys;
  162. this.last_domains = this.modelClass.data.domain;
  163. this.on_data_loaded(this.modelClass.data.data, group_bys);
  164. },
  165. on_data_loaded: function (events, group_bys) {
  166. var self = this;
  167. var ids = _.pluck(events, "id");
  168. return this._rpc({
  169. model: this.modelName,
  170. method: 'name_get',
  171. args: [
  172. ids,
  173. ],
  174. context: this.getSession().user_context,
  175. }).then(function(names) {
  176. var nevents = _.map(events, function (event) {
  177. return _.extend({
  178. __name: _.detect(names, function (name) {
  179. return name[0] === event.id;
  180. })[1]
  181. }, event);
  182. });
  183. return self.on_data_loaded_2(nevents, group_bys);
  184. });
  185. },
  186. on_data_loaded_2: function (events, group_bys) {
  187. var self = this;
  188. var data = [];
  189. var groups = [];
  190. this.grouped_by = group_bys;
  191. _.each(events, function (event) {
  192. if (event[self.date_start]) {
  193. data.push(self.event_data_transform(event));
  194. }
  195. });
  196. groups = this.split_groups(events, group_bys);
  197. this.timeline.setGroups(groups);
  198. this.timeline.setItems(data);
  199. if (!this.mode || this.mode == 'fit'){
  200. this.timeline.fit();
  201. }
  202. },
  203. // get the groups
  204. split_groups: function (events, group_bys) {
  205. if (group_bys.length === 0) {
  206. return events;
  207. }
  208. var groups = [];
  209. groups.push({id: -1, content: _t('-')});
  210. _.each(events, function (event) {
  211. var group_name = event[_.first(group_bys)];
  212. if (group_name) {
  213. if (group_name instanceof Array) {
  214. var group = _.find(groups, function (group) {
  215. return _.isEqual(group.id, group_name[0]);
  216. });
  217. if (group == null) {
  218. group = {id: group_name[0], content: group_name[1]};
  219. groups.push(group);
  220. }
  221. }
  222. }
  223. });
  224. return groups;
  225. },
  226. /* Transform Odoo event object to timeline event object */
  227. event_data_transform: function (evt) {
  228. var self = this;
  229. var date_start = new moment();
  230. var date_stop = null;
  231. var date_delay = evt[this.date_delay] || false,
  232. all_day = this.all_day ? evt[this.all_day] : false;
  233. if (all_day) {
  234. date_start = time.auto_str_to_date(evt[this.date_start].split(' ')[0], 'start');
  235. if (this.no_period) {
  236. date_stop = date_start;
  237. } else {
  238. date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop].split(' ')[0], 'stop') : null;
  239. }
  240. } else {
  241. date_start = time.auto_str_to_date(evt[this.date_start]);
  242. date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop]) : null;
  243. }
  244. if (date_start) {
  245. }
  246. if (!date_stop && date_delay) {
  247. date_stop = moment(date_start).add(date_delay, 'hours').toDate();
  248. }
  249. var group = evt[self.last_group_bys[0]];
  250. if (group && group instanceof Array) {
  251. group = _.first(group);
  252. } else {
  253. group = -1;
  254. }
  255. _.each(self.colors, function (color) {
  256. if (eval("'" + evt[color.field] + "' " + color.opt + " '" + color.value + "'")) {
  257. self.color = color.color;
  258. }
  259. });
  260. var r = {
  261. 'start': date_start,
  262. 'content': evt.__name != undefined ? evt.__name : evt.display_name,
  263. 'id': evt.id,
  264. 'group': group,
  265. 'evt': evt,
  266. 'style': 'background-color: ' + self.color + ';'
  267. };
  268. // Check if the event is instantaneous, if so, display it with a point on the timeline (no 'end')
  269. if (date_stop && !moment(date_start).isSame(date_stop)) {
  270. r.end = date_stop;
  271. }
  272. self.color = null;
  273. return r;
  274. },
  275. on_update: function (item, callback) {
  276. this._trigger(item, callback, 'onUpdate');
  277. },
  278. on_move: function (item, callback) {
  279. this._trigger(item, callback, 'onMove');
  280. },
  281. on_remove: function (item, callback) {
  282. this._trigger(item, callback, 'onRemove');
  283. },
  284. on_add: function (item, callback) {
  285. this._trigger(item, callback, 'onAdd');
  286. },
  287. _trigger: function (item, callback, trigger) {
  288. this.trigger_up(trigger, {
  289. 'item': item,
  290. 'callback': callback,
  291. 'rights': this.modelClass.data.rights,
  292. 'renderer': this,
  293. });
  294. },
  295. });
  296. return CalendarRenderer;
  297. });