From 4f0b76b04000d27f61fbd2a1934754e6a8020685 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Tue, 2 Aug 2016 00:59:05 +0200 Subject: [PATCH] [MIG] web_timeline: Migration to 9.0 --- web_timeline/README.rst | 117 ++++-- web_timeline/__openerp__.py | 11 +- web_timeline/models/ir_view.py | 15 +- web_timeline/static/description/icon.png | Bin 0 -> 12001 bytes web_timeline/static/src/css/web_timeline.css | 20 - web_timeline/static/src/js/web_timeline.js | 362 ++++++++----------- web_timeline/views/web_timeline.xml | 26 +- 7 files changed, 264 insertions(+), 287 deletions(-) create mode 100644 web_timeline/static/description/icon.png diff --git a/web_timeline/README.rst b/web_timeline/README.rst index e634fd9c..6d1ca0ab 100755 --- a/web_timeline/README.rst +++ b/web_timeline/README.rst @@ -2,48 +2,106 @@ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 -=============== -Timeline Widget -=============== +============= +Timeline view +============= -Define a new widget displaying events in an interactive visualization chart. +Define a new view displaying events in an interactive visualization chart. The widget is based on the external library http://visjs.org/timeline_examples.html -Usage -===== +Configuration +============= + +You need to define a view with the tag as base element. These are +the possible attributes for the tag: + +* date_start (required): it defines the name of the field of type date that + contains the start of the event. +* date_end (optional): it defines the name of the field of type date that + contains the end of the event. +* date_delay (optional): it defines the name of the field of type date that + contains the end of the event. +* default_group_by (required): it defines the name of the field that will be + taken as default group by when accessing the view or when no other group by + is selected. +* event_open_popup (optional): when set to true, it allows to edit the events + in a popup. If not (default value), the record is edited changing to form + view. +* colors (optional): it allows to set certain specific colors if the expressed + condition (JS syntax) is met. + +You also need to declare the view in an action window of the involved model. Example: + +.. code-block:: xml + - - - - - project.task.timeline - project.task - timeline - - - - - - - - - kanban,tree,form,calendar,gantt,timeline,graph - - - + + + project.task + timeline + + + + + + + + kanban,tree,form,calendar,gantt,timeline,graph + + + +Usage +===== + +For accessing the timeline view, you have to click on the button with the clock +icon in the view switcher. The first time you access to it, the timeline window +is zoomed to fit all the current elements, the same as when you perform a +search, filter or group by operation. + +You can use the mouse scroll to zoom in or out in the timeline, and click on +any free area and drag for panning the view in that direction. + +The records of your model will be shown as rectangles whose widths are the +duration of the event according our definition. You can select them clicking +on this rectangle. You can also use Ctrl or Shift keys for adding discrete +or range selections. Selected records are hightlighted with a different color +(but the difference will be more noticeable depending on the background color). +Once selected, you can drag and move the selected records across the timeline. + +When a record is selected, a red cross button appears on the upper left corner +that allows to remove that record. This doesn't work for multiple records +although they were selected. + +Records are grouped in different blocks depending on the group by criteria +selected (if none is specified, then the default group by is applied). +Dragging a record from one block to another change the corresponding field to +the value that represents the block. You can also click on the group name to +edit the involved record directly. + +Double-click on the record to edit it. Double-click in open area to create a +new record with the group and start date linked to the area you clicked in. + .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot :target: https://runbot.odoo-community.org/runbot/162/8.0 +Known issues / Roadmap +====================== + +* Implement support for vis.js timeline range item addition (with Ctrl key + pressed). +* Implement a more efficient way of refreshing timeline after a record update. + Bug Tracker =========== @@ -65,6 +123,7 @@ Contributors * Laurent Mignon * Adrien Peiffer +* Pedro M. Baeza Maintainer ---------- diff --git a/web_timeline/__openerp__.py b/web_timeline/__openerp__.py index 57ee01b0..5ed4ad64 100644 --- a/web_timeline/__openerp__.py +++ b/web_timeline/__openerp__.py @@ -4,16 +4,15 @@ { 'name': "Web timeline", - 'summary': """ - Interactive visualization chart to visualize events in time - """, - "version": "8.0.1.0.0", + 'summary': "Interactive visualization chart to show events in time", + "version": "9.0.1.0.0", 'author': 'ACSONE SA/NV,' + 'Tecnativa,' 'Odoo Community Association (OCA)', - "category": "Tools", + "category": "web", "website": "http://acsone.eu", 'depends': [ - 'web' + 'web', ], 'qweb': [ 'static/src/xml/web_timeline.xml', diff --git a/web_timeline/models/ir_view.py b/web_timeline/models/ir_view.py index 899d4873..bbf56e96 100644 --- a/web_timeline/models/ir_view.py +++ b/web_timeline/models/ir_view.py @@ -2,8 +2,7 @@ # Copyright 2016 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models -from openerp import api +from openerp import fields, models TIMELINE_VIEW = ('timeline', 'Timeline') @@ -12,14 +11,4 @@ TIMELINE_VIEW = ('timeline', 'Timeline') class IrUIView(models.Model): _inherit = 'ir.ui.view' - @api.model - def _setup_fields(self): - """Hack due since the field 'type' is not defined with the new api. - """ - cls = type(self) - type_selection = cls._fields['type'].selection - if TIMELINE_VIEW not in type_selection: - tmp = list(type_selection) - tmp.append(TIMELINE_VIEW) - cls._fields['type'].selection = tuple(set(tmp)) - super(IrUIView, self)._setup_fields() + type = fields.Selection(selection_add=[TIMELINE_VIEW]) diff --git a/web_timeline/static/description/icon.png b/web_timeline/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d01494920cc5393b0b44ccf97dc7f8be6967a2dd GIT binary patch literal 12001 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 literal 0 HcmV?d00001 diff --git a/web_timeline/static/src/css/web_timeline.css b/web_timeline/static/src/css/web_timeline.css index 79cbed61..898e7cf2 100644 --- a/web_timeline/static/src/css/web_timeline.css +++ b/web_timeline/static/src/css/web_timeline.css @@ -2,32 +2,12 @@ .openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_timeline:after { content: "N"; } -.timeline-navigation-zoom-in .ui-icon{ - background: none !important; -} - -.timeline-navigation-zoom-out .ui-icon{ - background: none !important; -} -.timeline-navigation-move-left .ui-icon{ - background: none !important; -} -.timeline-navigation-move-right .ui-icon{ - background: none !important; -} -/*.vis.timeline .timeaxis .grid.odd { - background: #f5f5f5; -} */ /* gray background in weekends, white text color */ .vis.timeline .timeaxis .grid.saturday, .vis.timeline .timeaxis .grid.sunday { background: gray; } -/* .vis.timeline .timeaxis .text.saturday, -.vis.timeline .timeaxis .text.sunday { - color: white; -} */ .vis.timeline .item.range .content { overflow: visible; diff --git a/web_timeline/static/src/js/web_timeline.js b/web_timeline/static/src/js/web_timeline.js index 6d63c6ea..9abee030 100644 --- a/web_timeline/static/src/js/web_timeline.js +++ b/web_timeline/static/src/js/web_timeline.js @@ -1,7 +1,7 @@ -/*--------------------------------------------------------- - * Odoo web_timeline +/* Odoo web_timeline * Copyright 2015 ACSONE SA/NV - *---------------------------------------------------------*/ + * Copyright 2016 Pedro M. Baeza + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ _.str.toBoolElse = function (str, elseValues, trueValues, falseValues) { var ret = _.str.toBool(str, trueValues, falseValues); @@ -11,21 +11,32 @@ _.str.toBoolElse = function (str, elseValues, trueValues, falseValues) { return ret; }; -openerp.web_timeline = function(instance) { - var _t = instance.web._t, - _lt = instance.web._lt, - QWeb = instance.web.qweb; +odoo.define('web_timeline.TimelineView', function (require) { + "use strict"; + + var core = require('web.core'); + var form_common = require('web.form_common'); + var Model = require('web.DataModel'); + var time = require('web.time'); + var View = require('web.View'); + var widgets = require('web_calendar.widgets'); + var _ = require('_'); + var $ = require('$'); + + var _t = core._t; + var _lt = core._lt; + var QWeb = core.qweb; function isNullOrUndef(value) { return _.isUndefined(value) || _.isNull(value); } - instance.web.views.add('timeline', 'instance.web_timeline.TimelineView'); - - instance.web_timeline.TimelineView = instance.web.View.extend({ + var TimelineView = View.extend({ template: "TimelineView", display_name: _lt('Timeline'), - quick_create_instance: 'instance.web_timeline.QuickCreate', + icon: 'fa-clock-o', + quick_create_instance: widgets.QuickCreate, + init: function (parent, dataset, view_id, options) { this._super(parent); this.ready = $.Deferred(); @@ -48,7 +59,7 @@ openerp.web_timeline = function(instance) { var promise = self.permissions[name]; if(!promise) { var defer = $.Deferred(); - new instance.web.Model(this.dataset.model) + new Model(this.dataset.model) .call("check_access_rights", [name, false]) .then(function (value) { self.permissions[name] = value; @@ -84,14 +95,14 @@ openerp.web_timeline = function(instance) { this.fields_view = fv; this.parse_colors(); this.$timeline = this.$el.find(".oe_timeline_widget"); - this.$el.find(".oe_timeline_button_today").click(self.on_today_clicked); - this.$el.find(".oe_timeline_button_scale_day").click(self.on_scale_day_clicked); - this.$el.find(".oe_timeline_button_scale_week").click(self.on_scale_week_clicked); - this.$el.find(".oe_timeline_button_scale_month").click(self.on_scale_month_clicked); - this.$el.find(".oe_timeline_button_scale_year").click(self.on_scale_year_clicked); + this.$el.find(".oe_timeline_button_today").click($.proxy(this.on_today_clicked, this)); + this.$el.find(".oe_timeline_button_scale_day").click($.proxy(this.on_scale_day_clicked, this)); + this.$el.find(".oe_timeline_button_scale_week").click($.proxy(this.on_scale_week_clicked, this)); + this.$el.find(".oe_timeline_button_scale_month").click($.proxy(this.on_scale_month_clicked, this)); + this.$el.find(".oe_timeline_button_scale_year").click($.proxy(this.on_scale_year_clicked, this)); this.current_window = { - start: new Date(), - end : new Date().addHours(24), + start: new moment(), + end : new moment().add(24, 'hours'), } this.info_fields = []; @@ -104,45 +115,43 @@ openerp.web_timeline = function(instance) { this.name = fv.name || attrs.string; this.view_id = fv.view_id; - this.start = py.eval(attrs.start || 'None', instance.web.pyeval.context()); - this.mode = attrs.mode; // one of month, week or day - this.date_start = attrs.date_start; // Field name of starting - // date field + this.mode = attrs.mode; + this.date_start = attrs.date_start; this.date_stop = attrs.date_stop; - + if (!isNullOrUndef(attrs.quick_create_instance)) { self.quick_create_instance = 'instance.' + attrs.quick_create_instance; } - // If this field is set ot true, we don't open the event in form + // If this field is set ot true, we don't open the event in form // view, but in a popup with the view_id passed by this parameter if (isNullOrUndef(attrs.event_open_popup) || !_.str.toBoolElse(attrs.event_open_popup, true)) { this.open_popup_action = false; } else { this.open_popup_action = attrs.event_open_popup; } - + this.fields = fv.fields; for (var fld = 0; fld < fv.arch.children.length; fld++) { this.info_fields.push(fv.arch.children[fld].attrs.name); } - var fields_get = new instance.web.Model(this.dataset.model) + var fields_get = new Model(this.dataset.model) .call('fields_get') .then(function (fields) { self.fields = fields; }); - var unlink_check = new instance.web.Model(this.dataset.model) + var unlink_check = new Model(this.dataset.model) .call("check_access_rights", ["unlink", false]) .then(function (unlink_right) { self.unlink_right = unlink_right; }); - var edit_check = new instance.web.Model(this.dataset.model) + var edit_check = new Model(this.dataset.model) .call("check_access_rights", ["write", false]) .then(function (write_right) { self.write_right = write_right; - + }); var init = function () { self.init_timeline().then(function() { @@ -151,9 +160,8 @@ openerp.web_timeline = function(instance) { self.ready.resolve(); }); }; - - var test = $.when(self.fields_get, self.get_perm('unlink'), self.get_perm('write'), self.get_perm('create')); - return $.when(test).then(init); + + return $.when(self.fields_get, self.get_perm('unlink'), self.get_perm('write'), self.get_perm('create')).then(init); }, init_timeline: function() { @@ -161,10 +169,14 @@ openerp.web_timeline = function(instance) { var options = { groupOrder: self.group_order, editable: { - add: self.permissions['create'], // add new items by double tapping - updateTime: self.permissions['write'], // drag items horizontally - updateGroup: self.permissions['write'], // drag items from one group to another - remove: self.permissions['unlink'], // delete an item by tapping the delete button top right + // add new items by double tapping + add: self.permissions['create'], + // drag items horizontally + updateTime: self.permissions['write'], + // drag items from one group to another + updateGroup: self.permissions['write'], + // delete an item by tapping the delete button top right + remove: self.permissions['unlink'], }, orientation: 'both', selectable: true, @@ -174,7 +186,6 @@ openerp.web_timeline = function(instance) { onUpdate: self.on_update, onRemove: self.on_remove, orientation: 'both', - start: self.start, }; self.timeline = new vis.Timeline(self.$timeline.empty().get(0)); self.timeline.setOptions(options); @@ -195,14 +206,14 @@ openerp.web_timeline = function(instance) { return +1; } return grp1.content - grp2.content; - + }, - /** - * Transform OpenERP event object to timeline event object - */ + /* Transform Odoo event object to timeline event object */ event_data_transform: function(evt) { var self = this; + var date_start = new moment(); + var date_stop = new moment(); var date_delay = evt[this.date_delay] || 1.0, all_day = this.all_day ? evt[this.all_day] : false, @@ -211,19 +222,19 @@ openerp.web_timeline = function(instance) { attendees = []; if (!all_day) { - date_start = instance.web.auto_str_to_date(evt[this.date_start]); - date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop]) : null; + date_start = time.auto_str_to_date(evt[this.date_start]); + date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop]) : null; } else { - date_start = instance.web.auto_str_to_date(evt[this.date_start].split(' ')[0],'start'); - date_stop = this.date_stop ? instance.web.auto_str_to_date(evt[this.date_stop].split(' ')[0],'stop') : null; + date_start = time.auto_str_to_date(evt[this.date_start].split(' ')[0],'start'); + date_stop = this.date_stop ? time.auto_str_to_date(evt[this.date_stop].split(' ')[0],'stop') : null; } - + if (!date_start){ - date_start = new Date(); + date_start = new moment(); } if(!date_stop) { - date_stop = date_start.clone().addHours(date_delay); + date_stop = moment(date_start).add(date_delay, 'hours').toDate(); } var group = evt[self.last_group_bys[0]]; if (group){ @@ -238,17 +249,16 @@ openerp.web_timeline = function(instance) { var r = { 'start': date_start, 'end': date_stop, - 'content': evt.__name, + 'content': evt.__name != undefined ? evt.__name : evt.display_name, 'id': evt.id, 'group': group, 'evt': evt, 'style': 'background-color: ' + self.color + ';', - + }; self.color = undefined; return r; }, - do_search: function (domains, contexts, group_bys) { var self = this; @@ -279,7 +289,6 @@ openerp.web_timeline = function(instance) { }); }, - reload: function() { var self = this; if (this.last_domains !== undefined){ @@ -288,34 +297,34 @@ openerp.web_timeline = function(instance) { } }, - on_data_loaded: function(tasks, group_bys) { + on_data_loaded: function(events, group_bys) { var self = this; - var ids = _.pluck(tasks, "id"); + var ids = _.pluck(events, "id"); return this.dataset.name_get(ids).then(function(names) { - var ntasks = _.map(tasks, function(task) { - return _.extend({__name: _.detect(names, function(name) { return name[0] == task.id; })[1]}, task); + var nevents = _.map(events, function(event) { + return _.extend({__name: _.detect(names, function(name) { return name[0] == event.id; })[1]}, event); }); - return self.on_data_loaded_2(ntasks, group_bys); + return self.on_data_loaded_2(nevents, group_bys); }); }, - on_data_loaded_2: function(tasks, group_bys) { + on_data_loaded_2: function(events, group_bys) { var self = this; var data = []; var groups = []; - _.each(tasks, function(event) { + _.each(events, function(event) { if (event[self.date_start]){ data.push(self.event_data_transform(event)); } }); - // get the groups - var split_groups = function(tasks, group_bys) { + // get the groups + var split_groups = function(events, group_bys) { if (group_bys.length === 0) - return tasks; + return events; var groups = []; groups.push({id:-1, content: _t('-')}) - _.each(tasks, function(task) { - var group_name = task[_.first(group_bys)]; + _.each(events, function(event) { + var group_name = event[_.first(group_bys)]; if (group_name) { var group = _.find(groups, function(group) { return _.isEqual(group.id, group_name[0]); }); if (group === undefined) { @@ -326,10 +335,10 @@ openerp.web_timeline = function(instance) { }); return groups; } - var groups = split_groups(tasks, group_bys); + var groups = split_groups(events, group_bys); this.timeline.setGroups(groups); this.timeline.setItems(data); - this.timeline.setWindow(this.current_window); + this.timeline.fit(); }, do_show: function() { @@ -343,55 +352,46 @@ openerp.web_timeline = function(instance) { } return this._super(action); }, - /** - * Handles a newly created record - * - * @param {id} id of the newly created record - */ - quick_created: function (id) { - - /** - * Note: it's of the most utter importance NOT to use inplace - * modification on this.dataset.ids as reference to this data is - * spread out everywhere in the various widget. Some of these - * reference includes values that should trigger action upon - * modification. - */ + + create_completed: function(id) { + var self = this; this.dataset.ids = this.dataset.ids.concat([id]); this.dataset.trigger("dataset_changed", id); - this.refresh_event(id); + this.dataset.read_ids([id], this.fields).done(function(records) { + var new_event = self.event_data_transform(records[0]); + var items = self.timeline.itemsData; + items.add(new_event); + self.timeline.setItems(items); + }); }, on_add: function(item, callback) { - var self = this, - pop = new instance.web.form.SelectCreatePopup(this), - context = this.get_popup_context(item); - pop.on("elements_selected", self, function(element_ids) { - self.reload().then(function() { - self.timeline.focus(element_ids); - }); - }); - pop.select_element( - self.dataset.model, - { - title: _t("Create"), - initial_view: "form", - }, - null, - context - ); + var self = this; + var context = this.dataset.get_context(); + // Initialize default values for creation + var default_context = {} + default_context['default_'.concat(this.date_start)] = item.start; + default_context['default_'.concat(this.date_stop)] = moment(item.start).add(1, 'hours').toDate(); + if (item.group > 0) { + default_context['default_'.concat(this.last_group_bys[0])] = item.group; + } + context.add(default_context); + // Show popup + var dialog = new form_common.FormViewDialog(this, { + res_model: this.dataset.model, + res_id: null, + context: context, + view_id: +this.open_popup_action, + }).open(); + dialog.on('create_completed', this, this.create_completed); + return false; }, - get_popup_context: function(item) { - var context = {}; - context['default_'.concat(this.date_start)] = item.start; - context['default_'.concat(this.date_stop)] = item.start.clone() - .addHours(this.date_delay || 1); - if(item.group != -1) - { - context['default_'.concat(this.last_group_bys[0])] = item.group; - } - return context; + write_completed: function(id) { + this.dataset.trigger("dataset_changed", id); + this.current_window = this.timeline.getWindow(); + this.reload(); + this.timeline.setWindow(this.current_window); }, on_update: function(item, callback) { @@ -408,47 +408,14 @@ openerp.web_timeline = function(instance) { } } else { - var id_cast = parseInt(id).toString() == id ? parseInt(id) : id; - var pop = new instance.web.form.FormOpenPopup(self); - pop.on('write_completed', self, self.reload); - pop.show_element( - self.dataset.model, - id_cast, - null, - {readonly: true, title: title} - ); - var form_controller = pop.view_form; - form_controller.on("load_record", self, function() { - var footer = pop.$el.closest(".modal").find(".modal-footer"); - footer.find('.oe_form_button_edit,.oe_form_button_save').remove(); - footer.find(".oe_form_button_cancel").prev().remove(); - footer.find('.oe_form_button_cancel').before(" or "); - button_edit = _.str.sprintf("",_t("Edit")); - button_save = _.str.sprintf("",_t("Save")); - footer.prepend(button_edit + button_save); - footer.find('.oe_form_button_save').hide(); - footer.find('.oe_form_button_edit').on('click', function() { - form_controller.to_edit_mode(); - footer.find('.oe_form_button_edit,.oe_form_button_save').toggle(); - }); - footer.find('.oe_form_button_save').on('click', function() { - form_controller.save(); - form_controller.to_view_mode(); - footer.find('.oe_form_button_edit,.oe_form_button_save').toggle(); - }); - var chatter = pop.$el.closest(".modal").find(".oe_chatter"); - if(chatter.length){ - var chatter_toggler = $($.parseHTML(_.str.sprintf('
%s
', _t("Messages")))); - chatter.before(chatter_toggler) - var chatter_content = chatter_toggler.find(".oe_chatter_content"); - chatter_content.prepend(chatter); - chatter_content.toggle(); - chatter_toggler.click(function(){ - chatter_content.toggle(); - chatter_toggler.toggleClass('fa-plus-circle fa-minus-circle'); - }); - } - }); + var dialog = new form_common.FormViewDialog(this, { + res_model: this.dataset.model, + res_id: parseInt(id).toString() == id ? parseInt(id) : id, + context: this.dataset.get_context(), + title: title, + view_id: +this.open_popup_action, + }).open(); + dialog.on('write_completed', this, this.write_completed); } return false; }, @@ -463,14 +430,12 @@ openerp.web_timeline = function(instance) { } var data = {}; data[self.fields_view.arch.attrs.date_start] = - instance.web.auto_date_to_str(start, self.fields[self.fields_view.arch.attrs.date_start].type); - data[self.fields_view.arch.attrs.date_stop] = - instance.web.auto_date_to_str(end, self.fields[self.fields_view.arch.attrs.date_stop].type); - data[self.fields_view.arch.attrs.default_group_by] = group; + time.auto_date_to_str(start, self.fields[self.fields_view.arch.attrs.date_start].type); + data[self.fields_view.arch.attrs.date_stop] = + time.auto_date_to_str(end, self.fields[self.fields_view.arch.attrs.date_stop].type); + data[self.fields_view.arch.attrs.default_group_by] = group; var id = item.evt.id; - this.dataset.write(id, data).then(function() { - self.reload(); - }); + this.dataset.write(id, data); }, on_remove: function(item, callback) { @@ -497,7 +462,7 @@ openerp.web_timeline = function(instance) { }, on_group_click: function(e) { - if(e.group == -1) + if (e.group == -1) { return; } @@ -510,55 +475,42 @@ openerp.web_timeline = function(instance) { }); }, + scale_current_window: function(factor){ + if (this.timeline){ + this.current_window = this.timeline.getWindow(); + this.current_window.end = moment(this.current_window.start).add(factor, 'hours'); + this.timeline.setWindow(this.current_window); + } + }, + on_today_clicked: function(){ this.current_window = { - start: new Date(), - end : new Date().addHours(24), - } - - if (this.timeline){ - this.timeline.setWindow(this.current_window); - } + start: new moment(), + end : new moment().add(24, 'hours'), + } + + if (this.timeline) { + this.timeline.setWindow(this.current_window); + } + }, + + on_scale_day_clicked: function(){ + this.scale_current_window(24); + }, + + on_scale_week_clicked: function(){ + this.scale_current_window(24 * 7); }, - - get_middel_date: function(start, end){ - //Get 1 hour in milliseconds - var one_hour=1000*60*60; - - // Convert both dates to milliseconds - var date1_ms = start.getTime(); - var date2_ms = end.getTime(); - - // Calculate the difference in milliseconds - var difference_ms = date2_ms - date1_ms; - - // Convert back to days and return - nb_hours = Math.round(difference_ms/one_hour); - return start.clone().addHours(nb_hours/2) - }, - - scale_current_window: function(factor){ - if (this.timeline){ - this.current_window = this.timeline.getWindow(); - this.current_window.end = this.current_window.start.clone().addHours(factor); - this.timeline.setWindow(this.current_window); - } - }, - - on_scale_day_clicked: function(){ - this.scale_current_window(24); - }, - - on_scale_week_clicked: function(){ - this.scale_current_window(24 * 7); - }, - - on_scale_month_clicked: function(){ - this.scale_current_window(24 * 30); - }, - on_scale_year_clicked: function(){ - this.scale_current_window(24 * 365); - }, + on_scale_month_clicked: function(){ + this.scale_current_window(24 * 30); + }, + + on_scale_year_clicked: function(){ + this.scale_current_window(24 * 365); + }, }); -}; + + core.view_registry.add('timeline', TimelineView); + return TimelineView; +}); diff --git a/web_timeline/views/web_timeline.xml b/web_timeline/views/web_timeline.xml index 4291e25a..95f6367a 100644 --- a/web_timeline/views/web_timeline.xml +++ b/web_timeline/views/web_timeline.xml @@ -1,16 +1,14 @@ - - - - - - +