Holger Brunn
7 years ago
No known key found for this signature in database
GPG Key ID: 1C9760FECA3AE18
12 changed files with 356 additions and 0 deletions
-
1web_widget_date_interval/DESCRIPTION.rst
-
28web_widget_date_interval/DEVELOP.rst
-
1web_widget_date_interval/README.rst
-
4web_widget_date_interval/ROADMAP.rst
-
3web_widget_date_interval/__init__.py
-
23web_widget_date_interval/__manifest__.py
-
13web_widget_date_interval/demo/res_users.xml
-
BINweb_widget_date_interval/static/description/icon.png
-
8web_widget_date_interval/static/src/css/web_widget_date_interval.css
-
245web_widget_date_interval/static/src/js/web_widget_date_interval.js
-
21web_widget_date_interval/static/src/xml/web_widget_date_interval.xml
-
9web_widget_date_interval/views/templates.xml
@ -0,0 +1 @@ |
|||
This module was written to add support for a date interval widget. |
@ -0,0 +1,28 @@ |
|||
Search views |
|||
============ |
|||
|
|||
On search views, this addon allows developers to show a predefined range of intervals. Use ``widget="date_interval"`` and an options dictionary to configure the addon, at least a type of interval. Currently, the only supported interval type is `iso_week`: ``options="{'type': 'iso_week'}"``. |
|||
|
|||
Configuration options |
|||
--------------------- |
|||
|
|||
type |
|||
Possible values are ``iso_week`` (shows iso week numbers) |
|||
|
|||
date |
|||
The reference date from which to calculate intervals if different from `now` |
|||
|
|||
lookahead |
|||
The amount of intervals after `date` to offer to the user |
|||
|
|||
lookbehind |
|||
The amount of intervals before `date` to offer to the user |
|||
|
|||
exclusive |
|||
If truthy, selecting one interval will unselect all already selected intervals. If not set (default), selecting multiple intervals will yield a domain for all selected intervals |
|||
|
|||
cycle |
|||
If truthy, the currently selected interval will be used for setting the reference date. The effect is that you can cycle through intervals rapidly in combination with `exclusive = 1` and `dropdown = 1` |
|||
|
|||
dropdown |
|||
If truthy (default), the intervals to choose are shown as a dropdown menu |
@ -0,0 +1 @@ |
|||
/ |
@ -0,0 +1,4 @@ |
|||
- migrate the work done in https://github.com/OCA/web/pull/575 to 10 for the form view part |
|||
- support tree and kanban views |
|||
- support much more interval types (days, months, quarters...) |
|||
- invent some textual way to fill in some interval (-1w+3w for now - one week to now + three weeks) |
@ -0,0 +1,3 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
@ -0,0 +1,23 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Date interval widget", |
|||
"version": "10.0.1.0.0", |
|||
"author": "Therp BV,Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"category": "Hidden/Dependency", |
|||
"summary": "Widget for date intervals", |
|||
"depends": [ |
|||
'web', |
|||
], |
|||
"demo": [ |
|||
"demo/res_users.xml", |
|||
], |
|||
"data": [ |
|||
'views/templates.xml', |
|||
], |
|||
"qweb": [ |
|||
'static/src/xml/web_widget_date_interval.xml', |
|||
], |
|||
} |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<odoo> |
|||
<record id="view_users_search" model="ir.ui.view"> |
|||
<field name="model">res.users</field> |
|||
<field name="inherit_id" ref="base.view_users_search" /> |
|||
<field name="arch" type="xml"> |
|||
<xpath expr="." position="inside"> |
|||
<field name="login_date" widget="date_interval" options="{'type': 'iso_week', 'dropdown': 0, 'exclusive': 1, 'cycle': 1}" /> |
|||
<field name="create_date" widget="date_interval" options="{'type': 'iso_week', 'lookbehind': 104, 'lookahead': 1}" /> |
|||
</xpath> |
|||
</field> |
|||
</record> |
|||
</odoo> |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,8 @@ |
|||
div.o_date_interval > button > a |
|||
{ |
|||
color: #4c4c4c; |
|||
} |
|||
div.o_date_interval > button > a.selected |
|||
{ |
|||
font-weight: bold; |
|||
} |
@ -0,0 +1,245 @@ |
|||
//-*- coding: utf-8 -*-
|
|||
//Copyright 2018 Therp BV <https://therp.nl>
|
|||
//License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
|||
|
|||
odoo.define('web_widget_date_interval', function(require) { |
|||
var core = require('web.core'), |
|||
pyeval = require('web.pyeval'), |
|||
Widget = require('web.Widget'), |
|||
SearchView = require('web.SearchView'); |
|||
|
|||
var SearchWidgetDateInterval = Widget.extend({ |
|||
template: 'SearchView.DateInterval', |
|||
events: { |
|||
'click [data-id]': 'on_click_interval', |
|||
}, |
|||
_option_defaults: { |
|||
iso_week: { |
|||
lookbehind: 1, |
|||
lookahead: 10, |
|||
exclusive: false, |
|||
cycle: false, |
|||
}, |
|||
}, |
|||
options: { |
|||
type: 'iso_week', |
|||
dropdown: true, |
|||
}, |
|||
init: function(parent, field_name, string, options) { |
|||
this.searchview = parent; |
|||
this.string = string; |
|||
this.options = _.extend( |
|||
{}, this.options, this._option_defaults[options.type] || {}, |
|||
options |
|||
); |
|||
this.field = field_name; |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
start: function() { |
|||
this.searchview.query.on( |
|||
'add change remove reset', this.proxy('on_searchview_change') |
|||
); |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
get_intervals: function() { |
|||
var interval_function = _.str.sprintf( |
|||
'get_intervals_%s', this.options.type |
|||
); |
|||
if(!this[interval_function]) { |
|||
throw new Error('Unknown interval type given'); |
|||
} |
|||
return this[interval_function](); |
|||
}, |
|||
get_intervals_iso_week: function() { |
|||
var existing = this.searchview.query.findWhere({ |
|||
category: this.string, |
|||
}), |
|||
reference_date = moment( |
|||
!existing || !this.options.cycle |
|||
? this.options.date |
|||
: existing.get('field').get_domain().slice(-2, -1)[0][2] |
|||
), |
|||
start_date = reference_date.clone().startOf('isoWeek') |
|||
.subtract(this.options.lookbehind, 'weeks'), |
|||
stop_date = reference_date.clone().endOf('isoWeek') |
|||
.add(this.options.lookahead, 'weeks'), |
|||
current_date = start_date.clone(), |
|||
result = []; |
|||
|
|||
while(current_date.isBefore(stop_date)) { |
|||
result.push({ |
|||
name: current_date.year() === moment().year() |
|||
? _.str.sprintf(core._t('%s'), current_date.isoWeek()) |
|||
: _.str.sprintf( |
|||
core._t('%s / %s'), current_date.isoWeek(), |
|||
current_date.isoWeekYear() |
|||
), |
|||
_id: _.str.sprintf( |
|||
'%s-%s', this.field, current_date.format('YYYY-MM-DD') |
|||
), |
|||
start: current_date.format('YYYY-MM-DD'), |
|||
stop: current_date.add(1, 'weeks').format('YYYY-MM-DD'), |
|||
}); |
|||
} |
|||
return result; |
|||
}, |
|||
on_click_interval: function(e) { |
|||
var $this = jQuery(e.currentTarget); |
|||
return this._update_searchview( |
|||
$this.data('start'), $this.data('stop'), $this.text().trim() |
|||
); |
|||
}, |
|||
_create_facet: function(date_start, date_stop, label) { |
|||
var self = this; |
|||
|
|||
return { |
|||
field: { |
|||
get_domain: function() { |
|||
return [ |
|||
'&', |
|||
[self.field, '>=', date_start], |
|||
[self.field, '<', date_stop], |
|||
]; |
|||
}, |
|||
// eslint-disable-next-line no-empty-function
|
|||
get_context: function() {}, |
|||
// eslint-disable-next-line no-empty-function
|
|||
get_groupby: function() {}, |
|||
}, |
|||
category: this.string, |
|||
icon: 'fa-calendar', |
|||
values: [ |
|||
{ |
|||
label: _.str.sprintf( |
|||
'%s: %s', this.string, label |
|||
), |
|||
value: null, |
|||
}, |
|||
], |
|||
_id: _.str.sprintf( |
|||
'%s-%s', self.field, date_start |
|||
), |
|||
}; |
|||
}, |
|||
_update_searchview: function(date_start, date_stop, label, options) { |
|||
var facet = this._create_facet(date_start, date_stop, label), |
|||
existing = this.searchview.query.findWhere({ |
|||
category: this.string, |
|||
}); |
|||
if(existing) { |
|||
var is_removal = existing.get('_id').includes(facet._id); |
|||
|
|||
this.searchview.query.remove(existing, {silent: !is_removal}); |
|||
|
|||
if(!this.options.exclusive) { |
|||
// concatenate existing facet with ours
|
|||
var domain = [].concat( |
|||
['|'], facet.field.get_domain(), |
|||
existing.get('field').get_domain() |
|||
); |
|||
facet._id = _.str.sprintf( |
|||
'%s %s', facet._id, existing.get('_id') |
|||
); |
|||
facet.values = facet.values.concat(existing.get('values')); |
|||
facet.field.get_domain = function() { |
|||
return domain; |
|||
}; |
|||
} |
|||
|
|||
if(is_removal) { |
|||
return; |
|||
} |
|||
} |
|||
this.searchview.query.add(facet, options); |
|||
}, |
|||
on_searchview_change: function() { |
|||
var self = this; |
|||
this.$('[data-id]').removeClass('selected'); |
|||
if(this.options.cycle) { |
|||
this.renderElement(); |
|||
} |
|||
this.searchview.query.each(function(facet) { |
|||
if(facet.get('category') === self.string) { |
|||
self.$( |
|||
_.map( |
|||
facet.get('_id').split(' '), function(x) { |
|||
return _.str.sprintf('[data-id="%s"]', x); |
|||
} |
|||
).join(',') |
|||
).addClass('selected'); |
|||
} |
|||
}); |
|||
}, |
|||
facet_for_defaults: function(values) { |
|||
var self = this, |
|||
default_value = values[_.str.sprintf( |
|||
'date_interval_%s', this.field |
|||
)], |
|||
facet = false; |
|||
|
|||
if(!default_value) { |
|||
return; |
|||
} |
|||
|
|||
default_value = moment(default_value); |
|||
|
|||
_(this.get_intervals()).each(function(interval) { |
|||
if( |
|||
!moment(interval.start).isBefore(default_value) || |
|||
!moment(interval.stop).isAfter(default_value) |
|||
) { |
|||
return; |
|||
} |
|||
facet = self._create_facet( |
|||
interval.start, interval.stop, interval.name |
|||
); |
|||
}); |
|||
return facet; |
|||
}, |
|||
visible: function() { |
|||
return false; |
|||
}, |
|||
}); |
|||
|
|||
SearchView.include({ |
|||
init: function() { |
|||
this._super.apply(this, arguments); |
|||
this.date_intervals = []; |
|||
}, |
|||
start: function() { |
|||
var self = this, |
|||
deferreds = [this._super.apply(this, arguments)]; |
|||
|
|||
if(this.$buttons && !this.options.disable_date_interval) { |
|||
_(this.date_intervals).each(function(widget) { |
|||
deferreds.push(widget.appendTo(self.$buttons)); |
|||
}); |
|||
} |
|||
|
|||
return jQuery.when.apply(jQuery, deferreds); |
|||
}, |
|||
prepare_search_inputs: function() { |
|||
this._super.apply(this, arguments); |
|||
|
|||
var self = this; |
|||
|
|||
_.chain(this.fields_view.arch.children) |
|||
.filter(function(x) { |
|||
return x.tag === 'field' && x.attrs.widget === 'date_interval'; |
|||
}) |
|||
.each(function(x) { |
|||
var widget = new SearchWidgetDateInterval( |
|||
self, x.attrs.name, core._t(x.attrs.string) || |
|||
self.ViewManager.search_fields_view.fields[x.attrs.name] |
|||
.string, pyeval.py_eval(x.attrs.options || '{}') |
|||
); |
|||
self.date_intervals.push(widget); |
|||
self.search_fields.push(widget); |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
return { |
|||
search_widget_date_interval: SearchWidgetDateInterval, |
|||
}; |
|||
}); |
@ -0,0 +1,21 @@ |
|||
<templates> |
|||
<div t-name="SearchView.DateInterval" class="btn-group o_date_interval o_dropdown"> |
|||
<button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" t-att-data-toggle="widget.options.dropdown and 'dropdown' or ''"> |
|||
<span class="fa fa-calendar"></span> |
|||
<t t-esc="widget.string" /> |
|||
<span t-if="widget.options.dropdown" class="caret" /> |
|||
<t t-else="" t-att-data-date_interval="widget.field"> |
|||
<t t-foreach="widget.get_intervals()" t-as="interval"> |
|||
<a t-att-data-id="interval._id" t-att-data-start="interval.start" t-att-data-stop="interval.stop" href="#"><t t-esc="interval.name" /></a> |
|||
</t> |
|||
</t> |
|||
</button> |
|||
<ul t-if="widget.options.dropdown" class="dropdown-menu o_filters_menu" role="menu" t-att-data-date_interval="widget.field"> |
|||
<t t-foreach="widget.get_intervals()" t-as="interval"> |
|||
<li t-att-data-id="interval._id" t-att-data-start="interval.start" t-att-data-stop="interval.stop"> |
|||
<a href="#"><t t-esc="interval.name" /></a> |
|||
</li> |
|||
</t> |
|||
</ul> |
|||
</div> |
|||
</templates> |
@ -0,0 +1,9 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<odoo> |
|||
<template id="assets_backend" name="web_widget_date_interval assets" inherit_id="web.assets_backend"> |
|||
<xpath expr="." position="inside"> |
|||
<script type="text/javascript" src="/web_widget_date_interval/static/src/js/web_widget_date_interval.js"></script> |
|||
<link rel="stylesheet" href="/web_widget_date_interval/static/src/css/web_widget_date_interval.css"/> |
|||
</xpath> |
|||
</template> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue