publish muk_web_searchpanel - 12.0
165
web_view_searchpanel/LICENSE
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
160
web_view_searchpanel/README.rst
Normal file
@@ -0,0 +1,160 @@
|
||||
================
|
||||
MuK Search Panel
|
||||
================
|
||||
|
||||
With Odoo version 13 a new feature is added which allows kanban views to be
|
||||
extended by a search panel. This can be defined via XML and is then automatically
|
||||
added to the view. With this module the function is ported back to version 12.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To install this module, you need to:
|
||||
|
||||
Download the module and add it to your Odoo addons folder. Afterward, log on to
|
||||
your Odoo server and go to the Apps menu. Trigger the debug mode and update the
|
||||
list by clicking on the "Update Apps List" link. Now install the module by
|
||||
clicking on the install button.
|
||||
|
||||
Another way to install this module is via the package management for Python
|
||||
(`PyPI <https://pypi.org/project/pip/>`_).
|
||||
|
||||
To install our modules using the package manager make sure
|
||||
`odoo-autodiscover <https://pypi.org/project/odoo-autodiscover/>`_ is installed
|
||||
correctly. Then open a console and install the module by entering the following
|
||||
command:
|
||||
|
||||
``pip install --extra-index-url https://nexus.mukit.at/repository/odoo/simple <module>``
|
||||
|
||||
The module name consists of the Odoo version and the module name, where
|
||||
underscores are replaced by a dash.
|
||||
|
||||
**Module:**
|
||||
|
||||
``odoo<version>-addon-<module_name>``
|
||||
|
||||
**Example:**
|
||||
|
||||
``sudo -H pip3 install --extra-index-url https://nexus.mukit.at/repository/odoo/simple odoo11-addon-muk-utils``
|
||||
|
||||
Once the installation has been successfully completed, the app is already in the
|
||||
correct folder. Log on to your Odoo server and go to the Apps menu. Trigger the
|
||||
debug mode and update the list by clicking on the "Update Apps List" link. Now
|
||||
install the module by clicking on the install button.
|
||||
|
||||
The biggest advantage of this variant is that you can now also update the app
|
||||
using the "pip" command. To do this, enter the following command in your console:
|
||||
|
||||
``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple <module>``
|
||||
|
||||
When the process is finished, restart your server and update the application in
|
||||
Odoo. The steps are the same as for the installation only the button has changed
|
||||
from "Install" to "Upgrade".
|
||||
|
||||
You can also view available Apps directly in our `repository <https://nexus.mukit.at/#browse/browse:odoo>`_
|
||||
and find a more detailed installation guide on our `website <https://mukit.at/page/open-source>`_.
|
||||
|
||||
For modules licensed under OPL-1, you will receive access data when you purchase
|
||||
the module. If the modules were not purchased directly from
|
||||
`MuK IT <https://www.mukit.at/>`_ please contact our support (support@mukit.at)
|
||||
with a confirmation of purchase to receive the corresponding access data.
|
||||
|
||||
Upgrade
|
||||
============
|
||||
|
||||
To upgrade this module, you need to:
|
||||
|
||||
Download the module and add it to your Odoo addons folder. Restart the server
|
||||
and log on to your Odoo server. Select the Apps menu and upgrade the module by
|
||||
clicking on the upgrade button.
|
||||
|
||||
If you installed the module using the "pip" command, you can also update the
|
||||
module in the same way. Just type the following command into the console:
|
||||
|
||||
``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple <module>``
|
||||
|
||||
When the process is finished, restart your server and update the application in
|
||||
Odoo, just like you would normally.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
No additional configuration is needed to use this module.
|
||||
|
||||
Usage
|
||||
=============
|
||||
|
||||
This tool allows to quickly filter data on the basis of given fields. The fields
|
||||
are specified as direct children of the ``searchpanel`` with tag name ``field``,
|
||||
and the following attributes:
|
||||
|
||||
* ``name`` (mandatory) the name of the field to filter on
|
||||
* ``select`` determines the behavior and display.
|
||||
* ``groups``: restricts to specific users
|
||||
* ``string``: determines the label to display
|
||||
* ``icon``: specifies which icon is used
|
||||
* ``color``: determines the icon color
|
||||
|
||||
Possible values for the ``select`` attribute are
|
||||
|
||||
* ``one`` (default) at most one value can be selected. Supported field types are many2one and selection.
|
||||
* ``multi`` several values can be selected (checkboxes). Supported field types are many2one, many2many and selection.
|
||||
|
||||
Additional optional attributes are available in the ``multi`` case:
|
||||
|
||||
* ``domain``: determines conditions that the comodel records have to satisfy.
|
||||
|
||||
A domain might be used to express a dependency on another field (with select="one")
|
||||
of the search panel. Consider
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<searchpanel>
|
||||
<field name="department_id"/>
|
||||
<field name="manager_id" select="multi" domain="[('department_id', '=', department_id)]"/>
|
||||
<searchpanel/>
|
||||
|
||||
In the above example, the range of values for manager_id (manager names) available at screen
|
||||
will depend on the value currently selected for the field ``department_id``.
|
||||
|
||||
* ``groupby``: field name of the comodel (only available for many2one and many2many fields). Values will be grouped by that field.
|
||||
|
||||
* ``disable_counters``: default is false. If set to true the counters won't be computed.
|
||||
|
||||
This feature has been implemented in case performances would be too bad.
|
||||
|
||||
Another way to solve performance issues is to properly override the ``search_panel_select_multi_range`` method.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Mathias Markl <mathias.markl@mukit.at>
|
||||
|
||||
Images
|
||||
------------
|
||||
|
||||
Some pictures are based on or inspired by the icon set of Font Awesome:
|
||||
|
||||
* `Font Awesome <https://fontawesome.com>`_
|
||||
|
||||
Projects
|
||||
------------
|
||||
|
||||
Parts of the module are based on or inspired by:
|
||||
|
||||
* `Odoo <https://github.com/odoo/odoo>`_
|
||||
|
||||
Author & Maintainer
|
||||
-------------------
|
||||
|
||||
This module is maintained by the `MuK IT GmbH <https://www.mukit.at/>`_.
|
||||
|
||||
MuK IT is an Austrian company specialized in customizing and extending Odoo.
|
||||
We develop custom solutions for your individual needs to help you focus on
|
||||
your strength and expertise to grow your business.
|
||||
|
||||
If you want to get in touch please contact us via mail
|
||||
(sale@mukit.at) or visit our website (https://mukit.at).
|
||||
23
web_view_searchpanel/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
###################################################################################
|
||||
#
|
||||
# Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
#
|
||||
# This file is part of MuK Search Panel
|
||||
# (see https://mukit.at).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###################################################################################
|
||||
|
||||
from . import models
|
||||
54
web_view_searchpanel/__manifest__.py
Normal file
@@ -0,0 +1,54 @@
|
||||
###################################################################################
|
||||
#
|
||||
# Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
#
|
||||
# This file is part of MuK Search Panel
|
||||
# (see https://mukit.at).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###################################################################################
|
||||
|
||||
{
|
||||
'name': 'MuK Search Panel',
|
||||
'summary': 'Kanban Search Panel',
|
||||
'version': '12.0.2.0.1',
|
||||
'category': 'Extra Tools',
|
||||
'license': 'LGPL-3',
|
||||
'author': 'MuK IT',
|
||||
'website': 'https://www.mukit.at',
|
||||
'live_test_url': 'https://mukit.at/r/SgN',
|
||||
'contributors': [
|
||||
'Mathias Markl <mathias.markl@mukit.at>',
|
||||
],
|
||||
'depends': [
|
||||
'muk_web_utils',
|
||||
],
|
||||
'data': [
|
||||
"template/assets.xml",
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/*.xml',
|
||||
],
|
||||
'images': [
|
||||
'static/description/banner.png'
|
||||
],
|
||||
'external_dependencies': {
|
||||
'python': [],
|
||||
'bin': [],
|
||||
},
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
15
web_view_searchpanel/doc/changelog.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
`1.2.0`
|
||||
-------
|
||||
|
||||
- Mobile Support
|
||||
|
||||
`1.1.0`
|
||||
-------
|
||||
|
||||
- Lazy Loading on long requests
|
||||
|
||||
`1.0.0`
|
||||
-------
|
||||
|
||||
- Init version
|
||||
|
||||
160
web_view_searchpanel/doc/index.rst
Normal file
@@ -0,0 +1,160 @@
|
||||
================
|
||||
MuK Search Panel
|
||||
================
|
||||
|
||||
With Odoo version 13 a new feature is added which allows kanban views to be
|
||||
extended by a search panel. This can be defined via XML and is then automatically
|
||||
added to the view. With this module the function is ported back to version 12.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To install this module, you need to:
|
||||
|
||||
Download the module and add it to your Odoo addons folder. Afterward, log on to
|
||||
your Odoo server and go to the Apps menu. Trigger the debug mode and update the
|
||||
list by clicking on the "Update Apps List" link. Now install the module by
|
||||
clicking on the install button.
|
||||
|
||||
Another way to install this module is via the package management for Python
|
||||
(`PyPI <https://pypi.org/project/pip/>`_).
|
||||
|
||||
To install our modules using the package manager make sure
|
||||
`odoo-autodiscover <https://pypi.org/project/odoo-autodiscover/>`_ is installed
|
||||
correctly. Then open a console and install the module by entering the following
|
||||
command:
|
||||
|
||||
``pip install --extra-index-url https://nexus.mukit.at/repository/odoo/simple <module>``
|
||||
|
||||
The module name consists of the Odoo version and the module name, where
|
||||
underscores are replaced by a dash.
|
||||
|
||||
**Module:**
|
||||
|
||||
``odoo<version>-addon-<module_name>``
|
||||
|
||||
**Example:**
|
||||
|
||||
``sudo -H pip3 install --extra-index-url https://nexus.mukit.at/repository/odoo/simple odoo11-addon-muk-utils``
|
||||
|
||||
Once the installation has been successfully completed, the app is already in the
|
||||
correct folder. Log on to your Odoo server and go to the Apps menu. Trigger the
|
||||
debug mode and update the list by clicking on the "Update Apps List" link. Now
|
||||
install the module by clicking on the install button.
|
||||
|
||||
The biggest advantage of this variant is that you can now also update the app
|
||||
using the "pip" command. To do this, enter the following command in your console:
|
||||
|
||||
``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple <module>``
|
||||
|
||||
When the process is finished, restart your server and update the application in
|
||||
Odoo. The steps are the same as for the installation only the button has changed
|
||||
from "Install" to "Upgrade".
|
||||
|
||||
You can also view available Apps directly in our `repository <https://nexus.mukit.at/#browse/browse:odoo>`_
|
||||
and find a more detailed installation guide on our `website <https://mukit.at/page/open-source>`_.
|
||||
|
||||
For modules licensed under OPL-1, you will receive access data when you purchase
|
||||
the module. If the modules were not purchased directly from
|
||||
`MuK IT <https://www.mukit.at/>`_ please contact our support (support@mukit.at)
|
||||
with a confirmation of purchase to receive the corresponding access data.
|
||||
|
||||
Upgrade
|
||||
============
|
||||
|
||||
To upgrade this module, you need to:
|
||||
|
||||
Download the module and add it to your Odoo addons folder. Restart the server
|
||||
and log on to your Odoo server. Select the Apps menu and upgrade the module by
|
||||
clicking on the upgrade button.
|
||||
|
||||
If you installed the module using the "pip" command, you can also update the
|
||||
module in the same way. Just type the following command into the console:
|
||||
|
||||
``pip install --upgrade --extra-index-url https://nexus.mukit.at/repository/odoo/simple <module>``
|
||||
|
||||
When the process is finished, restart your server and update the application in
|
||||
Odoo, just like you would normally.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
No additional configuration is needed to use this module.
|
||||
|
||||
Usage
|
||||
=============
|
||||
|
||||
This tool allows to quickly filter data on the basis of given fields. The fields
|
||||
are specified as direct children of the ``searchpanel`` with tag name ``field``,
|
||||
and the following attributes:
|
||||
|
||||
* ``name`` (mandatory) the name of the field to filter on
|
||||
* ``select`` determines the behavior and display.
|
||||
* ``groups``: restricts to specific users
|
||||
* ``string``: determines the label to display
|
||||
* ``icon``: specifies which icon is used
|
||||
* ``color``: determines the icon color
|
||||
|
||||
Possible values for the ``select`` attribute are
|
||||
|
||||
* ``one`` (default) at most one value can be selected. Supported field types are many2one and selection.
|
||||
* ``multi`` several values can be selected (checkboxes). Supported field types are many2one, many2many and selection.
|
||||
|
||||
Additional optional attributes are available in the ``multi`` case:
|
||||
|
||||
* ``domain``: determines conditions that the comodel records have to satisfy.
|
||||
|
||||
A domain might be used to express a dependency on another field (with select="one")
|
||||
of the search panel. Consider
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<searchpanel>
|
||||
<field name="department_id"/>
|
||||
<field name="manager_id" select="multi" domain="[('department_id', '=', department_id)]"/>
|
||||
<searchpanel/>
|
||||
|
||||
In the above example, the range of values for manager_id (manager names) available at screen
|
||||
will depend on the value currently selected for the field ``department_id``.
|
||||
|
||||
* ``groupby``: field name of the comodel (only available for many2one and many2many fields). Values will be grouped by that field.
|
||||
|
||||
* ``disable_counters``: default is false. If set to true the counters won't be computed.
|
||||
|
||||
This feature has been implemented in case performances would be too bad.
|
||||
|
||||
Another way to solve performance issues is to properly override the ``search_panel_select_multi_range`` method.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Mathias Markl <mathias.markl@mukit.at>
|
||||
|
||||
Images
|
||||
------------
|
||||
|
||||
Some pictures are based on or inspired by the icon set of Font Awesome:
|
||||
|
||||
* `Font Awesome <https://fontawesome.com>`_
|
||||
|
||||
Projects
|
||||
------------
|
||||
|
||||
Parts of the module are based on or inspired by:
|
||||
|
||||
* `Odoo <https://github.com/odoo/odoo>`_
|
||||
|
||||
Author & Maintainer
|
||||
-------------------
|
||||
|
||||
This module is maintained by the `MuK IT GmbH <https://www.mukit.at/>`_.
|
||||
|
||||
MuK IT is an Austrian company specialized in customizing and extending Odoo.
|
||||
We develop custom solutions for your individual needs to help you focus on
|
||||
your strength and expertise to grow your business.
|
||||
|
||||
If you want to get in touch please contact us via mail
|
||||
(sale@mukit.at) or visit our website (https://mukit.at).
|
||||
24
web_view_searchpanel/models/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
###################################################################################
|
||||
#
|
||||
# Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
#
|
||||
# This file is part of MuK Search Panel
|
||||
# (see https://mukit.at).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###################################################################################
|
||||
|
||||
from . import base
|
||||
|
||||
196
web_view_searchpanel/models/base.py
Normal file
@@ -0,0 +1,196 @@
|
||||
###################################################################################
|
||||
#
|
||||
# Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
#
|
||||
# This file is part of MuK Search Panel
|
||||
# (see https://mukit.at).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###################################################################################
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import lazy
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Base(models.AbstractModel):
|
||||
|
||||
_inherit = 'base'
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Functions
|
||||
#----------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def search_panel_select_range(self, field_name, **kwargs):
|
||||
"""
|
||||
Return possible values of the field field_name (case select="one")
|
||||
and the parent field (if any) used to hierarchize them.
|
||||
|
||||
:param field_name: the name of a many2one category field
|
||||
:return: {
|
||||
'parent_field': parent field on the comodel of field, or False
|
||||
'values': array of dictionaries containing some info on the records
|
||||
available on the comodel of the field 'field_name'.
|
||||
The display name (and possibly parent_field) are fetched.
|
||||
}
|
||||
"""
|
||||
field = self._fields[field_name]
|
||||
supported_types = ['many2one']
|
||||
if field.type not in supported_types:
|
||||
raise UserError(_('Only types %(supported_types)s are supported for category (found type %(field_type)s)') % ({
|
||||
'supported_types': supported_types, 'field_type': field.type}))
|
||||
|
||||
Comodel = self.env[field.comodel_name]
|
||||
fields = ['display_name']
|
||||
parent_name = Comodel._parent_name if Comodel._parent_name in Comodel._fields else False
|
||||
if parent_name:
|
||||
fields.append(parent_name)
|
||||
|
||||
model_domain = expression.AND([
|
||||
kwargs.get('search_domain', []),
|
||||
kwargs.get('category_domain', []),
|
||||
kwargs.get('filter_domain', []),
|
||||
])
|
||||
|
||||
return {
|
||||
'parent_field': parent_name,
|
||||
'values': Comodel.with_context(hierarchical_naming=False).search_read(model_domain, fields),
|
||||
}
|
||||
|
||||
@api.model
|
||||
def search_panel_select_multi_range(self, field_name, **kwargs):
|
||||
"""
|
||||
Return possible values of the field field_name (case select="multi"),
|
||||
possibly with counters and groups.
|
||||
|
||||
:param field_name: the name of a filter field;
|
||||
possible types are many2one, many2many, selection.
|
||||
:param search_domain: base domain of search
|
||||
:param category_domain: domain generated by categories
|
||||
:param filter_domain: domain generated by filters
|
||||
:param comodel_domain: domain of field values (if relational)
|
||||
:param group_by: extra field to read on comodel, to group comodel records
|
||||
:param disable_counters: whether to count records by value
|
||||
:return: a list of possible values, each being a dict with keys
|
||||
'id' (value),
|
||||
'name' (value label),
|
||||
'count' (how many records with that value),
|
||||
'group_id' (value of group),
|
||||
'group_name' (label of group).
|
||||
"""
|
||||
field = self._fields[field_name]
|
||||
supported_types = ['many2one', 'many2many', 'selection']
|
||||
if field.type not in supported_types:
|
||||
raise UserError(_('Only types %(supported_types)s are supported for filter (found type %(field_type)s)') % ({
|
||||
'supported_types': supported_types, 'field_type': field.type}))
|
||||
|
||||
Comodel = self.env.get(field.comodel_name)
|
||||
|
||||
model_domain = expression.AND([
|
||||
kwargs.get('search_domain', []),
|
||||
kwargs.get('category_domain', []),
|
||||
kwargs.get('filter_domain', []),
|
||||
[(field_name, '!=', False)],
|
||||
])
|
||||
comodel_domain = kwargs.get('comodel_domain', [])
|
||||
disable_counters = kwargs.get('disable_counters', False)
|
||||
|
||||
group_by = kwargs.get('group_by', False)
|
||||
if group_by:
|
||||
# determine the labeling of values returned by the group_by field
|
||||
group_by_field = Comodel._fields[group_by]
|
||||
|
||||
if group_by_field.type == 'many2one':
|
||||
def group_id_name(value):
|
||||
return value or (False, _("Not Set"))
|
||||
|
||||
elif group_by_field.type == 'selection':
|
||||
desc = Comodel.fields_get([group_by])[group_by]
|
||||
group_by_selection = dict(desc['selection'])
|
||||
group_by_selection[False] = _("Not Set")
|
||||
|
||||
def group_id_name(value):
|
||||
return value, group_by_selection[value]
|
||||
|
||||
else:
|
||||
def group_id_name(value):
|
||||
return (value, value) if value else (False, _("Not Set"))
|
||||
|
||||
# get filter_values
|
||||
filter_values = []
|
||||
|
||||
if field.type == 'many2one':
|
||||
counters = {}
|
||||
if not disable_counters:
|
||||
groups = self.read_group(model_domain, [field_name], [field_name])
|
||||
counters = {
|
||||
group[field_name][0]: group[field_name + '_count']
|
||||
for group in groups
|
||||
}
|
||||
# retrieve all possible values, and return them with their label and counter
|
||||
field_names = ['display_name', group_by] if group_by else ['display_name']
|
||||
records = Comodel.search_read(comodel_domain, field_names)
|
||||
for record in records:
|
||||
record_id = record['id']
|
||||
values = {
|
||||
'id': record_id,
|
||||
'name': record['display_name'],
|
||||
'count': counters.get(record_id, 0),
|
||||
}
|
||||
if group_by:
|
||||
values['group_id'], values['group_name'] = group_id_name(record[group_by])
|
||||
filter_values.append(values)
|
||||
|
||||
elif field.type == 'many2many':
|
||||
# retrieve all possible values, and return them with their label and counter
|
||||
field_names = ['display_name', group_by] if group_by else ['display_name']
|
||||
records = Comodel.search_read(comodel_domain, field_names)
|
||||
for record in records:
|
||||
record_id = record['id']
|
||||
values = {
|
||||
'id': record_id,
|
||||
'name': record['display_name'],
|
||||
'count': 0,
|
||||
}
|
||||
if not disable_counters:
|
||||
count_domain = expression.AND([model_domain, [(field_name, 'in', record_id)]])
|
||||
values['count'] = self.search_count(count_domain)
|
||||
if group_by:
|
||||
values['group_id'], values['group_name'] = group_id_name(record[group_by])
|
||||
filter_values.append(values)
|
||||
|
||||
elif field.type == 'selection':
|
||||
counters = {}
|
||||
if not disable_counters:
|
||||
groups = self.read_group(model_domain, [field_name], [field_name])
|
||||
counters = {
|
||||
group[field_name]: group[field_name + '_count']
|
||||
for group in groups
|
||||
}
|
||||
# retrieve all possible values, and return them with their label and counter
|
||||
selection = self.fields_get([field_name])[field_name]
|
||||
for value, label in selection:
|
||||
filter_values.append({
|
||||
'id': value,
|
||||
'name': label,
|
||||
'count': counters.get(value, 0),
|
||||
})
|
||||
|
||||
return filter_values
|
||||
BIN
web_view_searchpanel/static/description/banner.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
web_view_searchpanel/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
1
web_view_searchpanel/static/description/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 250 250" width="250" height="250"><defs><clipPath id="_clipPath_KBOvcQMajkx6u1KjE87f6Wp7zy0dKIrq"><rect width="250" height="250"/></clipPath></defs><g clip-path="url(#_clipPath_KBOvcQMajkx6u1KjE87f6Wp7zy0dKIrq)"><rect width="250" height="250" style="fill:rgb(0,0,0)" fill-opacity="0"/><defs><filter id="X8vnKaUKqkYAytrIn8Oy6z1k1pFTQEna" x="-200%" y="-200%" width="400%" height="400%" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feOffset xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" dx="0" dy="3"/><feGaussianBlur xmlns="http://www.w3.org/2000/svg" stdDeviation="2.146804531419514" result="pf_100_offsetBlur"/><feComposite xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" in2="pf_100_offsetBlur" result="pf_100_inverse" operator="out"/><feFlood xmlns="http://www.w3.org/2000/svg" flood-color="#FFFFFF" flood-opacity="0.5" result="pf_100_color"/><feComposite xmlns="http://www.w3.org/2000/svg" in="pf_100_color" in2="pf_100_inverse" operator="in" result="pf_100_shadow"/><feComposite xmlns="http://www.w3.org/2000/svg" in="pf_100_shadow" in2="SourceGraphic" operator="over" result="_out_KfogRIB1Z3caelhojJWnoaONSPxivnX7"/><feOffset xmlns="http://www.w3.org/2000/svg" in="_out_KfogRIB1Z3caelhojJWnoaONSPxivnX7" dx="0" dy="-3"/><feGaussianBlur xmlns="http://www.w3.org/2000/svg" stdDeviation="2.146804531419514" result="pf_101_offsetBlur"/><feComposite xmlns="http://www.w3.org/2000/svg" in="_out_KfogRIB1Z3caelhojJWnoaONSPxivnX7" in2="pf_101_offsetBlur" result="pf_101_inverse" operator="out"/><feFlood xmlns="http://www.w3.org/2000/svg" flood-color="#000000" flood-opacity="0.5" result="pf_101_color"/><feComposite xmlns="http://www.w3.org/2000/svg" in="pf_101_color" in2="pf_101_inverse" operator="in" result="pf_101_shadow"/><feComposite xmlns="http://www.w3.org/2000/svg" in="pf_101_shadow" in2="_out_KfogRIB1Z3caelhojJWnoaONSPxivnX7" operator="over" result="_out_eBBUZkzmm3LPw5i93XsTLnBhP1IFjsIl"/><feMerge><feMergeNode in="_out_eBBUZkzmm3LPw5i93XsTLnBhP1IFjsIl"/></feMerge></filter></defs><g filter="url(#X8vnKaUKqkYAytrIn8Oy6z1k1pFTQEna)"><path d="M 15 0 L 235 0 C 243.279 0 250 6.721 250 15 L 250 235 C 250 243.279 243.279 250 235 250 L 15 250 C 6.721 250 0 243.279 0 235 L 0 15 C 0 6.721 6.721 0 15 0 Z" style="stroke:none;fill:#747474;stroke-miterlimit:10;"/></g><g filter="url(#NSA4ysqYcOG4M1ElpxVdrf63x27scAFh)"><path d=" M 108.94 196 L 198.536 196 C 202.737 196 206.143 192.594 206.143 188.393 L 206.143 163.036 C 206.143 158.834 202.737 155.429 198.536 155.429 L 108.94 155.429 C 104.739 155.429 101.333 158.834 101.333 163.036 L 101.333 188.393 C 101.333 192.594 104.739 196 108.94 196 Z " fill="rgb(255,255,255)"/><path d=" M 101.333 61.607 L 101.333 86.964 C 101.333 91.166 104.739 94.571 108.94 94.571 L 198.536 94.571 C 202.737 94.571 206.143 91.166 206.143 86.964 L 206.143 61.607 C 206.143 57.406 202.737 54 198.536 54 L 108.94 54 C 104.739 54 101.333 57.406 101.333 61.607 Z " fill="rgb(255,255,255)"/><path d=" M 81.625 54 L 52.042 54 C 47.14 54 43.167 58.614 43.167 64.306 L 43.167 185.694 C 43.167 191.386 47.14 196 52.042 196 L 81.625 196 C 86.527 196 90.5 191.386 90.5 185.694 L 90.5 64.306 C 90.5 58.614 86.527 54 81.625 54 Z " fill="rgb(255,255,255)"/><path d=" M 108.94 145.286 L 198.536 145.286 C 202.737 145.286 206.143 141.88 206.143 137.679 L 206.143 112.321 C 206.143 108.12 202.737 104.714 198.536 104.714 L 108.94 104.714 C 104.739 104.714 101.333 108.12 101.333 112.321 L 101.333 137.679 C 101.333 141.88 104.739 145.286 108.94 145.286 Z " fill="rgb(255,255,255)"/></g><defs><filter id="NSA4ysqYcOG4M1ElpxVdrf63x27scAFh" x="-200%" y="-200%" width="400%" height="400%" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feGaussianBlur xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" stdDeviation="2.146804531419514"/><feOffset xmlns="http://www.w3.org/2000/svg" dx="-5" dy="5" result="pf_100_offsetBlur"/><feFlood xmlns="http://www.w3.org/2000/svg" flood-color="#000000" flood-opacity="0.5"/><feComposite xmlns="http://www.w3.org/2000/svg" in2="pf_100_offsetBlur" operator="in" result="pf_100_dropShadow"/><feBlend xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" in2="pf_100_dropShadow" mode="normal"/></filter></defs></g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
124
web_view_searchpanel/static/description/index.html
Normal file
@@ -0,0 +1,124 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">MuK Search Panel</h2>
|
||||
<h3 class="oe_slogan">Kanban Search Panel</h3>
|
||||
<h4 class="oe_slogan" style="font-size: 23px;">MuK IT GmbH -
|
||||
www.mukit.at</h4>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div style="max-width: 84%; margin: 16px 8%;">
|
||||
<h3 class="oe_slogan">Overview</h3>
|
||||
<p class="oe_mt32">With Odoo version 13 a new feature is added
|
||||
which allows kanban views to be extended by a search panel. This can
|
||||
be defined via XML and is then automatically added to the view. With
|
||||
this module the function is ported back to version 12.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan">Our Services</h3>
|
||||
<div
|
||||
style="display: flex; padding-top: 20px; justify-content: space-between;">
|
||||
<div style="flex-basis: 18%;">
|
||||
<a href="https://mukit.at/r/MQ5" target="_blank">
|
||||
<div
|
||||
style="width: 75px; height: 75px; border-radius: 100%; margin: auto;">
|
||||
<img src="service_implementation.png"
|
||||
style="width: 100%; border-radius: 100%;">
|
||||
</div>
|
||||
<h3 class="oe_slogan"
|
||||
style="text-align: center; font-size: 14px; width: 100%; margin: 0; margin-top: 14px; color: #000 !important; margin-top: 5px; opacity: 1 !important; line-height: 17px;">
|
||||
Odoo <br>Implementation
|
||||
</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div style="flex-basis: 18%;">
|
||||
<a href="https://mukit.at/r/u7c" target="_blank">
|
||||
<div
|
||||
style="width: 75px; height: 75px; border-radius: 100%; margin: auto;">
|
||||
<img src="service_integration.png"
|
||||
style="width: 100%; border-radius: 100%;">
|
||||
</div>
|
||||
<h3 class="oe_slogan"
|
||||
style="text-align: center; font-size: 14px; width: 100%; margin: 0; margin-top: 14px; color: #000 !important; margin-top: 5px; opacity: 1 !important; line-height: 17px;">
|
||||
Odoo <br>Integration
|
||||
</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div style="flex-basis: 18%;">
|
||||
<a href="https://mukit.at/r/xDJ" target="_blank">
|
||||
<div
|
||||
style="width: 75px; height: 75px; border-radius: 100%; margin: auto;">
|
||||
<img src="service_customization.png"
|
||||
style="width: 100%; border-radius: 100%;">
|
||||
</div>
|
||||
<h3 class="oe_slogan"
|
||||
style="text-align: center; font-size: 14px; width: 100%; margin: 0; margin-top: 14px; color: #000 !important; margin-top: 5px; opacity: 1 !important; line-height: 17px;">
|
||||
Odoo <br>Customization
|
||||
</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div style="flex-basis: 18%;">
|
||||
<a href=" https://mukit.at/r/J3A" target="_blank">
|
||||
<div
|
||||
style="width: 75px; height: 75px; border-radius: 100%; margin: auto;">
|
||||
<img src="service_development.png"
|
||||
style="width: 100%; border-radius: 100%;">
|
||||
</div>
|
||||
<h3 class="oe_slogan"
|
||||
style="text-align: center; font-size: 14px; width: 100%; margin: 0; margin-top: 14px; color: #000 !important; margin-top: 5px; opacity: 1 !important; line-height: 17px;">
|
||||
Odoo <br>Development
|
||||
</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div style="flex-basis: 18%;">
|
||||
<a href=" https://mukit.at/r/R1v" target="_blank">
|
||||
<div
|
||||
style="width: 75px; height: 75px; border-radius: 100%; margin: auto;">
|
||||
<img src="service_support.png"
|
||||
style="width: 100%; border-radius: 100%;">
|
||||
</div>
|
||||
<h3 class="oe_slogan"
|
||||
style="text-align: center; font-size: 14px; width: 100%; margin: 0; margin-top: 14px; color: #000 !important; margin-top: 5px; opacity: 1 !important; line-height: 17px;">
|
||||
Odoo <br>Support
|
||||
</h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container" style="padding: 32px 0;">
|
||||
<div class="oe_row oe_spaced" style="margin: auto;">
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<a href="https://mukit.at/r/SgN" target="_blank"> <img
|
||||
src="preview.png">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<h3 class="oe_slogan">Help and Support</h3>
|
||||
<h5 class="oe_slogan" style="font-size: 20px;">Feel free to
|
||||
contact us, if you need any help with your Odoo integration or
|
||||
addiontal features.</h5>
|
||||
<div class="oe_slogan">
|
||||
<a class="btn btn-primary btn-lg mt8" href="https://mukit.at"
|
||||
target="_blank"> <i class="fa fa-globe"></i> Website
|
||||
</a> <a class="btn btn-primary btn-lg mt8" href="mailto:sale@mukit.at">
|
||||
<i class="fa fa-envelope"></i> Contact Us
|
||||
</a> <a class="btn btn-primary btn-lg mt8"
|
||||
href="https://github.com/muk-it" target="_blank"> <i
|
||||
class="fa fa-github"></i> Issues
|
||||
</a>
|
||||
</div>
|
||||
<img src="logo.png"
|
||||
style="width: 200px; margin-bottom: 20px; display: block;"
|
||||
class="mx-auto center-block">
|
||||
</section>
|
||||
BIN
web_view_searchpanel/static/description/logo.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
web_view_searchpanel/static/description/preview.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 27 KiB |
BIN
web_view_searchpanel/static/description/service_development.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 26 KiB |
BIN
web_view_searchpanel/static/description/service_integration.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
web_view_searchpanel/static/description/service_support.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
80
web_view_searchpanel/static/src/js/kanban_controller.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
*
|
||||
* This file is part of MuK Search Panel
|
||||
* (see https://mukit.at).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
odoo.define('muk_web_searchpanel.KanbanController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
var view_dialogs = require('web.view_dialogs');
|
||||
var viewUtils = require('web.viewUtils');
|
||||
|
||||
var Domain = require('web.Domain');
|
||||
var Context = require('web.Context');
|
||||
var KanbanController = require('web.KanbanController');
|
||||
|
||||
var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
|
||||
KanbanController.include({
|
||||
custom_events: _.extend({}, KanbanController.prototype.custom_events, {
|
||||
search_panel_domain_updated: '_onSearchPanelDomainUpdated',
|
||||
}),
|
||||
init: function (parent, model, renderer, params) {
|
||||
this._super.apply(this, arguments);
|
||||
this._searchPanel = params.searchPanel;
|
||||
this.controlPanelDomain = params.controlPanelDomain || [];
|
||||
this.searchPanelDomain = this._searchPanel ? this._searchPanel.getDomain() : [];
|
||||
},
|
||||
start: function () {
|
||||
if (this._searchPanel) {
|
||||
this.$el.addClass('o_kanban_with_searchpanel');
|
||||
this.$el.prepend(this._searchPanel.$el);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
update: function (params) {
|
||||
if (!this._searchPanel) {
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
var self = this;
|
||||
if (params.domain) {
|
||||
this.controlPanelDomain = params.domain;
|
||||
}
|
||||
params.noRender = true;
|
||||
params.domain = this.controlPanelDomain.concat(this.searchPanelDomain);
|
||||
var superProm = this._super.apply(this, arguments);
|
||||
var searchPanelProm = this._updateSearchPanel();
|
||||
return $.when(superProm, searchPanelProm).then(function () {
|
||||
return self.renderer.render();
|
||||
});
|
||||
},
|
||||
_updateSearchPanel: function () {
|
||||
return this._searchPanel.update({searchDomain: this.controlPanelDomain});
|
||||
},
|
||||
_onSearchPanelDomainUpdated: function (ev) {
|
||||
this.searchPanelDomain = ev.data.domain;
|
||||
this.reload({offset: 0});
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
41
web_view_searchpanel/static/src/js/kanban_renderer.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
*
|
||||
* This file is part of MuK Search Panel
|
||||
* (see https://mukit.at).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
odoo.define('muk_web_searchpanel.KanbanRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var session = require('web.session');
|
||||
|
||||
var KanbanRenderer = require('web.KanbanRenderer');
|
||||
|
||||
var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
|
||||
KanbanRenderer.include({
|
||||
render: function () {
|
||||
return this._render();
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
650
web_view_searchpanel/static/src/js/kanban_searchpanel.js
Normal file
@@ -0,0 +1,650 @@
|
||||
/**********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
*
|
||||
* This file is part of MuK Search Panel
|
||||
* (see https://mukit.at).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
odoo.define('web.SearchPanel', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var Domain = require('web.Domain');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var qweb = core.qweb;
|
||||
|
||||
var SearchPanel = Widget.extend({
|
||||
className: 'o_search_panel',
|
||||
events: {
|
||||
'click .o_search_panel_category_value header': '_onCategoryValueClicked',
|
||||
'click .o_search_panel_category_value .o_toggle_fold': '_onToggleFoldCategory',
|
||||
'click .o_search_panel_filter_group .o_toggle_fold': '_onToggleFoldFilterGroup',
|
||||
'change .o_search_panel_filter_value > div > input': '_onFilterValueChanged',
|
||||
'change .o_search_panel_filter_group > div > input': '_onFilterGroupChanged',
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} params
|
||||
* @param {Object} [params.defaultCategoryValues={}] the category value to
|
||||
* activate by default, for each category
|
||||
* @param {Object} params.fields
|
||||
* @param {string} params.model
|
||||
* @param {Object} params.sections
|
||||
* @param {Array[]} params.searchDomain domain coming from controlPanel
|
||||
*/
|
||||
init: function (parent, params) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.categories = _.pick(params.sections, function (section) {
|
||||
return section.type === 'category';
|
||||
});
|
||||
this.filters = _.pick(params.sections, function (section) {
|
||||
return section.type === 'filter';
|
||||
});
|
||||
|
||||
this.defaultCategoryValues = params.defaultCategoryValues || {};
|
||||
this.fields = params.fields;
|
||||
this.model = params.model;
|
||||
this.searchDomain = params.searchDomain;
|
||||
|
||||
this.loadProm = $.Deferred();
|
||||
this.loadPromLazy = true;
|
||||
},
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
var loading = $.Deferred();
|
||||
var loadPromTimer = setTimeout(function () {
|
||||
if(loading.state() !== 'resolved') {
|
||||
loading.resolve();
|
||||
}
|
||||
}, this.loadPromMaxTime || 1000);
|
||||
this._fetchCategories().then(function () {
|
||||
self._fetchFilters().then(function () {
|
||||
if(loading.state() !== 'resolved') {
|
||||
clearTimeout(loadPromTimer);
|
||||
self.loadPromLazy = false;
|
||||
loading.resolve();
|
||||
}
|
||||
self.loadProm.resolve();
|
||||
});
|
||||
});
|
||||
return $.when(loading, this._super.apply(this, arguments));
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
if(this.loadProm.state() !== 'resolved') {
|
||||
this.$el.html($("<div/>", {
|
||||
'class': "o_search_panel_loading",
|
||||
'html': "<i class='fa fa-spinner fa-pulse' />"
|
||||
}));
|
||||
}
|
||||
this.loadProm.then(function() {
|
||||
self._render();
|
||||
});
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Array[]} the current searchPanel domain based on active
|
||||
* categories and checked filters
|
||||
*/
|
||||
getDomain: function () {
|
||||
return this._getCategoryDomain().concat(this._getFilterDomain());
|
||||
},
|
||||
/**
|
||||
* Reload the filters and re-render. Note that we only reload the filters if
|
||||
* the controlPanel domain or searchPanel domain has changed.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {Array[]} params.searchDomain domain coming from controlPanel
|
||||
* @returns {$.Promise}
|
||||
*/
|
||||
update: function (params) {
|
||||
if(this.loadProm.state() === 'resolved') {
|
||||
var newSearchDomainStr = JSON.stringify(params.searchDomain);
|
||||
var currentSearchDomainStr = JSON.stringify(this.searchDomain);
|
||||
if (this.needReload || (currentSearchDomainStr !== newSearchDomainStr)) {
|
||||
this.needReload = false;
|
||||
this.searchDomain = params.searchDomain;
|
||||
this._fetchFilters().then(this._render.bind(this));
|
||||
} else {
|
||||
this._render();
|
||||
}
|
||||
}
|
||||
return $.when();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} categoryId
|
||||
* @param {Object[]} values
|
||||
*/
|
||||
_createCategoryTree: function (categoryId, values) {
|
||||
var category = this.categories[categoryId];
|
||||
var parentField = category.parentField;
|
||||
|
||||
category.values = {};
|
||||
values.forEach(function (value) {
|
||||
category.values[value.id] = _.extend({}, value, {
|
||||
childrenIds: [],
|
||||
folded: true,
|
||||
parentId: value[parentField] && value[parentField][0] || false,
|
||||
});
|
||||
});
|
||||
Object.keys(category.values).forEach(function (valueId) {
|
||||
var value = category.values[valueId];
|
||||
if (value.parentId && category.values[value.parentId]) {
|
||||
category.values[value.parentId].childrenIds.push(value.id);
|
||||
} else {
|
||||
value.parentId = false;
|
||||
value[parentField] = false;
|
||||
}
|
||||
});
|
||||
category.rootIds = Object.keys(category.values).filter(function (valueId) {
|
||||
var value = category.values[valueId];
|
||||
return value.parentId === false;
|
||||
});
|
||||
category.activeValueId = false;
|
||||
|
||||
if(!this.loadPromLazy) {
|
||||
// set active value
|
||||
var validValues = _.pluck(category.values, 'id').concat([false]);
|
||||
// set active value from context
|
||||
var value = this.defaultCategoryValues[category.fieldName];
|
||||
// if not set in context, or set to an unknown value, set active value
|
||||
// from localStorage
|
||||
if (!_.contains(validValues, value)) {
|
||||
var storageKey = this._getLocalStorageKey(category);
|
||||
value = this.call('local_storage', 'getItem', storageKey);
|
||||
}
|
||||
|
||||
// if not set in localStorage either, select 'All'
|
||||
category.activeValueId = _.contains(validValues, value) ? value : false;
|
||||
|
||||
// unfold ancestor values of active value to make it is visible
|
||||
if (category.activeValueId) {
|
||||
var parentValueIds = this._getAncestorValueIds(category, category.activeValueId);
|
||||
parentValueIds.forEach(function (parentValue) {
|
||||
category.values[parentValue].folded = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} filterId
|
||||
* @param {Object[]} values
|
||||
*/
|
||||
_createFilterTree: function (filterId, values) {
|
||||
var filter = this.filters[filterId];
|
||||
|
||||
// restore checked property
|
||||
values.forEach(function (value) {
|
||||
var oldValue = filter.values && filter.values[value.id];
|
||||
value.checked = oldValue && oldValue.checked || false;
|
||||
});
|
||||
|
||||
filter.values = {};
|
||||
var groupIds = [];
|
||||
if (filter.groupBy) {
|
||||
var groups = {};
|
||||
values.forEach(function (value) {
|
||||
var groupId = value.group_id;
|
||||
if (!groups[groupId]) {
|
||||
if (groupId) {
|
||||
groupIds.push(groupId);
|
||||
}
|
||||
groups[groupId] = {
|
||||
folded: false,
|
||||
id: groupId,
|
||||
name: value.group_name,
|
||||
values: {},
|
||||
tooltip: value.group_tooltip,
|
||||
sequence: value.group_sequence,
|
||||
sortedValueIds: [],
|
||||
};
|
||||
// restore former checked and folded state
|
||||
var oldGroup = filter.groups && filter.groups[groupId];
|
||||
groups[groupId].state = oldGroup && oldGroup.state || false;
|
||||
groups[groupId].folded = oldGroup && oldGroup.folded || false;
|
||||
}
|
||||
groups[groupId].values[value.id] = value;
|
||||
groups[groupId].sortedValueIds.push(value.id);
|
||||
});
|
||||
filter.groups = groups;
|
||||
filter.sortedGroupIds = _.sortBy(groupIds, function (groupId) {
|
||||
return groups[groupId].sequence || groups[groupId].name;
|
||||
});
|
||||
Object.keys(filter.groups).forEach(function (groupId) {
|
||||
filter.values = _.extend(filter.values, filter.groups[groupId].values);
|
||||
});
|
||||
} else {
|
||||
values.forEach(function (value) {
|
||||
filter.values[value.id] = value;
|
||||
});
|
||||
filter.sortedValueIds = values.map(function (value) {
|
||||
return value.id;
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Fetch values for each category. This is done only once, at startup.
|
||||
*
|
||||
* @private
|
||||
* @returns {$.Promise} resolved when all categories have been fetched
|
||||
*/
|
||||
_fetchCategories: function () {
|
||||
var self = this;
|
||||
var defs = Object.keys(this.categories).map(function (categoryId) {
|
||||
var category = self.categories[categoryId];
|
||||
var field = self.fields[category.fieldName];
|
||||
var def;
|
||||
if (field.type === 'selection') {
|
||||
var values = field.selection.map(function (value) {
|
||||
return {id: value[0], display_name: value[1]};
|
||||
});
|
||||
def = $.when(values);
|
||||
} else {
|
||||
var categoryDomain = self._getCategoryDomain();
|
||||
var filterDomain = self._getFilterDomain();
|
||||
def = self._rpc({
|
||||
method: 'search_panel_select_range',
|
||||
model: self.model,
|
||||
args: [category.fieldName],
|
||||
kwargs: {
|
||||
category_domain: categoryDomain,
|
||||
filter_domain: filterDomain,
|
||||
search_domain: self.searchDomain,
|
||||
},
|
||||
}, {
|
||||
shadow: true,
|
||||
}).then(function (result) {
|
||||
category.parentField = result.parent_field;
|
||||
return result.values;
|
||||
});
|
||||
}
|
||||
return def.then(function (values) {
|
||||
self._createCategoryTree(categoryId, values);
|
||||
});
|
||||
});
|
||||
return $.when.apply($, defs);
|
||||
},
|
||||
/**
|
||||
* Fetch values for each filter. This is done at startup, and at each reload
|
||||
* (when the controlPanel or searchPanel domain changes).
|
||||
*
|
||||
* @private
|
||||
* @returns {$.Promise} resolved when all filters have been fetched
|
||||
*/
|
||||
_fetchFilters: function () {
|
||||
var self = this;
|
||||
var evalContext = {};
|
||||
Object.keys(this.categories).forEach(function (categoryId) {
|
||||
var category = self.categories[categoryId];
|
||||
evalContext[category.fieldName] = category.activeValueId;
|
||||
});
|
||||
var categoryDomain = this._getCategoryDomain();
|
||||
var filterDomain = this._getFilterDomain();
|
||||
var defs = Object.keys(this.filters).map(function (filterId) {
|
||||
var filter = self.filters[filterId];
|
||||
return self._rpc({
|
||||
method: 'search_panel_select_multi_range',
|
||||
model: self.model,
|
||||
args: [filter.fieldName],
|
||||
kwargs: {
|
||||
category_domain: categoryDomain,
|
||||
comodel_domain: Domain.prototype.stringToArray(filter.domain, evalContext),
|
||||
disable_counters: filter.disableCounters,
|
||||
filter_domain: filterDomain,
|
||||
group_by: filter.groupBy || false,
|
||||
search_domain: self.searchDomain,
|
||||
},
|
||||
}, {
|
||||
shadow: true,
|
||||
}).then(function (values) {
|
||||
self._createFilterTree(filterId, values);
|
||||
});
|
||||
});
|
||||
return $.when.apply($, defs);
|
||||
},
|
||||
/**
|
||||
* Compute and return the domain based on the current active categories.
|
||||
*
|
||||
* @private
|
||||
* @returns {Array[]}
|
||||
*/
|
||||
_getCategoryDomain: function () {
|
||||
var self = this;
|
||||
function categoryToDomain(domain, categoryId) {
|
||||
var category = self.categories[categoryId];
|
||||
if (category.activeValueId) {
|
||||
domain.push([category.fieldName, '=', category.activeValueId]);
|
||||
} else if(self.loadPromLazy && self.loadProm.state() !== 'resolved') {
|
||||
var value = self.defaultCategoryValues[category.fieldName];
|
||||
if (value) {
|
||||
domain.push([category.fieldName, '=', value]);
|
||||
}
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
return Object.keys(this.categories).reduce(categoryToDomain, []);
|
||||
},
|
||||
/**
|
||||
* Compute and return the domain based on the current checked filters.
|
||||
* The values of a single filter are combined using a simple rule: checked values within
|
||||
* a same group are combined with an 'OR' (this is expressed as single condition using a list)
|
||||
* and groups are combined with an 'AND' (expressed by concatenation of conditions).
|
||||
* If a filter has no groups, its checked values are implicitely considered as forming
|
||||
* a group (and grouped using an 'OR').
|
||||
*
|
||||
* @private
|
||||
* @returns {Array[]}
|
||||
*/
|
||||
_getFilterDomain: function () {
|
||||
var self = this;
|
||||
function getCheckedValueIds(values) {
|
||||
return Object.keys(values).reduce(function (checkedValues, valueId) {
|
||||
if (values[valueId].checked) {
|
||||
checkedValues.push(values[valueId].id);
|
||||
}
|
||||
return checkedValues;
|
||||
}, []);
|
||||
}
|
||||
function filterToDomain(domain, filterId) {
|
||||
var filter = self.filters[filterId];
|
||||
if (filter.groups) {
|
||||
Object.keys(filter.groups).forEach(function (groupId) {
|
||||
var group = filter.groups[groupId];
|
||||
var checkedValues = getCheckedValueIds(group.values);
|
||||
if (checkedValues.length) {
|
||||
domain.push([filter.fieldName, 'in', checkedValues]);
|
||||
}
|
||||
});
|
||||
} else if (filter.values) {
|
||||
var checkedValues = getCheckedValueIds(filter.values);
|
||||
if (checkedValues.length) {
|
||||
domain.push([filter.fieldName, 'in', checkedValues]);
|
||||
}
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
return Object.keys(this.filters).reduce(filterToDomain, []);
|
||||
},
|
||||
/**
|
||||
* The active id of each category is stored in the localStorage, s.t. it
|
||||
* can be restored afterwards (when the action is reloaded, for instance).
|
||||
* This function returns the key in the sessionStorage for a given category.
|
||||
*
|
||||
* @param {Object} category
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLocalStorageKey: function (category) {
|
||||
return 'searchpanel_' + this.model + '_' + category.fieldName;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} category
|
||||
* @param {integer} categoryValueId
|
||||
* @returns {integer[]} list of ids of the ancestors of the given value in
|
||||
* the given category
|
||||
*/
|
||||
_getAncestorValueIds: function (category, categoryValueId) {
|
||||
var categoryValue = category.values[categoryValueId];
|
||||
var parentId = categoryValue.parentId;
|
||||
if (parentId) {
|
||||
return [parentId].concat(this._getAncestorValueIds(category, parentId));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
|
||||
// sort categories and filters according to their index
|
||||
var categories = Object.keys(this.categories).map(function (categoryId) {
|
||||
return self.categories[categoryId];
|
||||
});
|
||||
var filters = Object.keys(this.filters).map(function (filterId) {
|
||||
return self.filters[filterId];
|
||||
});
|
||||
var sections = categories.concat(filters).sort(function (s1, s2) {
|
||||
return s1.index - s2.index;
|
||||
});
|
||||
|
||||
sections.forEach(function (section) {
|
||||
if (Object.keys(section.values).length) {
|
||||
if (section.type === 'category') {
|
||||
self.$el.append(self._renderCategory(section));
|
||||
} else {
|
||||
self.$el.append(self._renderFilter(section));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} category
|
||||
* @returns {string}
|
||||
*/
|
||||
_renderCategory: function (category) {
|
||||
return qweb.render('SearchPanel.Category', {category: category});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} filter
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
_renderFilter: function (filter) {
|
||||
var $filter = $(qweb.render('SearchPanel.Filter', {filter: filter}));
|
||||
|
||||
// set group inputs in indeterminate state when necessary
|
||||
Object.keys(filter.groups || {}).forEach(function (groupId) {
|
||||
var state = filter.groups[groupId].state;
|
||||
// group 'false' is not displayed
|
||||
if (groupId !== 'false' && state === 'indeterminate') {
|
||||
$filter
|
||||
.find('.o_search_panel_filter_group[data-group-id=' + groupId + '] input')
|
||||
.get(0)
|
||||
.indeterminate = true;
|
||||
}
|
||||
});
|
||||
|
||||
return $filter;
|
||||
},
|
||||
/**
|
||||
* Compute the current searchPanel domain based on categories and filters,
|
||||
* and notify environment of the domain change.
|
||||
*
|
||||
* Note that this assumes that the environment will update the searchPanel.
|
||||
* This is done as such to ensure the coordination between the reloading of
|
||||
* the searchPanel and the reloading of the data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_notifyDomainUpdated: function () {
|
||||
this.needReload = true;
|
||||
this.trigger_up('search_panel_domain_updated', {
|
||||
domain: this.getDomain(),
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onCategoryValueClicked: function (ev) {
|
||||
ev.stopPropagation();
|
||||
var $item = $(ev.currentTarget).closest('.o_search_panel_category_value');
|
||||
var category = this.categories[$item.data('categoryId')];
|
||||
var valueId = $item.data('id') || false;
|
||||
category.activeValueId = valueId;
|
||||
var storageKey = this._getLocalStorageKey(category);
|
||||
this.call('local_storage', 'setItem', storageKey, valueId);
|
||||
this._notifyDomainUpdated();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onFilterGroupChanged: function (ev) {
|
||||
ev.stopPropagation();
|
||||
var $item = $(ev.target).closest('.o_search_panel_filter_group');
|
||||
var filter = this.filters[$item.data('filterId')];
|
||||
var groupId = $item.data('groupId');
|
||||
var group = filter.groups[groupId];
|
||||
group.state = group.state === 'checked' ? 'unchecked' : 'checked';
|
||||
Object.keys(group.values).forEach(function (valueId) {
|
||||
group.values[valueId].checked = group.state === 'checked';
|
||||
});
|
||||
this._notifyDomainUpdated();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onFilterValueChanged: function (ev) {
|
||||
ev.stopPropagation();
|
||||
var $item = $(ev.target).closest('.o_search_panel_filter_value');
|
||||
var valueId = $item.data('valueId');
|
||||
var filter = this.filters[$item.data('filterId')];
|
||||
var value = filter.values[valueId];
|
||||
value.checked = !value.checked;
|
||||
var group = filter.groups && filter.groups[value.group_id];
|
||||
if (group) {
|
||||
var valuePartition = _.partition(Object.keys(group.values), function (valueId) {
|
||||
return group.values[valueId].checked;
|
||||
});
|
||||
if (valuePartition[0].length && valuePartition[1].length) {
|
||||
group.state = 'indeterminate';
|
||||
} else if (valuePartition[0].length) {
|
||||
group.state = 'checked';
|
||||
} else {
|
||||
group.state = 'unchecked';
|
||||
}
|
||||
}
|
||||
this._notifyDomainUpdated();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onToggleFoldCategory: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var $item = $(ev.currentTarget).closest('.o_search_panel_category_value');
|
||||
var category = this.categories[$item.data('categoryId')];
|
||||
var valueId = $item.data('id');
|
||||
category.values[valueId].folded = !category.values[valueId].folded;
|
||||
this._render();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onToggleFoldFilterGroup: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var $item = $(ev.currentTarget).closest('.o_search_panel_filter_group');
|
||||
var filter = this.filters[$item.data('filterId')];
|
||||
var groupId = $item.data('groupId');
|
||||
filter.groups[groupId].folded = !filter.groups[groupId].folded;
|
||||
this._render();
|
||||
},
|
||||
});
|
||||
|
||||
if (config.device.isMobile) {
|
||||
SearchPanel.include({
|
||||
tagName: 'details',
|
||||
|
||||
_getCategorySelection: function () {
|
||||
var self = this;
|
||||
return Object.keys(this.categories).reduce(function (selection, categoryId) {
|
||||
var category = self.categories[categoryId];
|
||||
console.log('category', category);
|
||||
if (category.activeValueId) {
|
||||
var ancestorIds = [category.activeValueId].concat(self._getAncestorValueIds(category, category.activeValueId));
|
||||
var breadcrumb = ancestorIds.map(function (valueId) {
|
||||
return category.values[valueId].display_name;
|
||||
});
|
||||
selection.push({ breadcrumb: breadcrumb, icon: category.icon, color: category.color});
|
||||
}
|
||||
console.log('selection', selection);
|
||||
return selection;
|
||||
}, []);
|
||||
},
|
||||
|
||||
_getFilterSelection: function () {
|
||||
var self = this;
|
||||
return Object.keys(this.filters).reduce(function (selection, filterId) {
|
||||
var filter = self.filters[filterId];
|
||||
console.log('filter', filter);
|
||||
if (filter.groups) {
|
||||
Object.keys(filter.groups).forEach(function (groupId) {
|
||||
var group = filter.groups[groupId];
|
||||
Object.keys(group.values).forEach(function (valueId) {
|
||||
var value = group.values[valueId];
|
||||
if (value.checked) {
|
||||
selection.push({name: value.name, icon: filter.icon, color: filter.color});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (filter.values) {
|
||||
Object.keys(filter.values).forEach(function (valueId) {
|
||||
var value = filter.values[valueId];
|
||||
if (value.checked) {
|
||||
selection.push({name: value.name, icon: filter.icon, color: filter.color});
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log('selection', selection);
|
||||
return selection;
|
||||
}, []);
|
||||
},
|
||||
|
||||
_render: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.prepend(qweb.render('SearchPanel.MobileSummary', {
|
||||
categories: this._getCategorySelection(),
|
||||
filterValues: this._getFilterSelection(),
|
||||
separator: ' / ',
|
||||
}));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return SearchPanel;
|
||||
|
||||
});
|
||||
133
web_view_searchpanel/static/src/js/kanban_view.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
*
|
||||
* This file is part of MuK Search Panel
|
||||
* (see https://mukit.at).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
odoo.define('muk_web_searchpanel.KanbanView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var pyUtils = require('web.py_utils');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var KanbanView = require('web.KanbanView');
|
||||
var SearchPanel = require('web.SearchPanel');
|
||||
|
||||
var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
|
||||
KanbanView.include({
|
||||
config: _.extend({}, KanbanView.prototype.config, {
|
||||
SearchPanel: SearchPanel,
|
||||
}),
|
||||
init: function (viewInfo, params) {
|
||||
this.searchPanelSections = Object.create(null);
|
||||
this._super.apply(this, arguments);
|
||||
this.hasSearchPanel = !_.isEmpty(this.searchPanelSections);
|
||||
|
||||
},
|
||||
getController: function (parent) {
|
||||
var self = this;
|
||||
var def = undefined;
|
||||
if (this.hasSearchPanel) {
|
||||
def = this._createSearchPanel(parent);
|
||||
}
|
||||
var _super = this._super.bind(this);
|
||||
return $.when(def).then(function (searchPanel) {
|
||||
return _super(parent).done(function (controller) {
|
||||
if (self.hasSearchPanel) {
|
||||
self.controllerParams.searchPanel.setParent(controller);
|
||||
}
|
||||
return controller
|
||||
});
|
||||
});
|
||||
},
|
||||
_createSearchPanel: function (parent) {
|
||||
var self = this;
|
||||
var defaultCategoryValues = {};
|
||||
Object.keys(this.loadParams.context).forEach(function (key) {
|
||||
var match = /^searchpanel_default_(.*)$/.exec(key);
|
||||
if (match) {
|
||||
defaultCategoryValues[match[1]] = self.loadParams.context[key];
|
||||
}
|
||||
});
|
||||
var controlPanelDomain = this.loadParams.domain;
|
||||
var searchPanel = new this.config.SearchPanel(parent, {
|
||||
defaultCategoryValues: defaultCategoryValues,
|
||||
fields: this.fields,
|
||||
model: this.loadParams.modelName,
|
||||
searchDomain: controlPanelDomain,
|
||||
sections: this.searchPanelSections,
|
||||
});
|
||||
this.controllerParams.searchPanel = searchPanel;
|
||||
this.controllerParams.controlPanelDomain = controlPanelDomain;
|
||||
return searchPanel.appendTo(document.createDocumentFragment()).then(function () {
|
||||
var searchPanelDomain = searchPanel.getDomain();
|
||||
self.loadParams.domain = controlPanelDomain.concat(searchPanelDomain);
|
||||
});
|
||||
},
|
||||
_processNode: function (node, fv) {
|
||||
if (node.tag === 'searchpanel') {
|
||||
// if (!config.device.isMobile) {
|
||||
// this._processSearchPanelNode(node, fv);
|
||||
// }
|
||||
this._processSearchPanelNode(node, fv);
|
||||
return false;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
_processSearchPanelNode: function (node, fv) {
|
||||
var self = this;
|
||||
node.children.forEach(function (childNode, index) {
|
||||
if (childNode.tag !== 'field') {
|
||||
return;
|
||||
}
|
||||
if (childNode.attrs.invisible === "1") {
|
||||
return;
|
||||
}
|
||||
var fieldName = childNode.attrs.name;
|
||||
var type = childNode.attrs.select === 'multi' ? 'filter' : 'category';
|
||||
|
||||
var sectionId = _.uniqueId('section_');
|
||||
var section = {
|
||||
color: childNode.attrs.color,
|
||||
description: childNode.attrs.string || fv.fields[fieldName].string,
|
||||
fieldName: fieldName,
|
||||
icon: childNode.attrs.icon,
|
||||
id: sectionId,
|
||||
index: index,
|
||||
type: type,
|
||||
};
|
||||
if (section.type === 'category') {
|
||||
section.icon = section.icon || 'fa-folder';
|
||||
} else if (section.type === 'filter') {
|
||||
section.disableCounters = !!pyUtils.py_eval(childNode.attrs.disable_counters || '0');
|
||||
section.domain = childNode.attrs.domain || '[]';
|
||||
section.groupBy = childNode.attrs.groupby;
|
||||
section.icon = section.icon || 'fa-filter';
|
||||
}
|
||||
self.searchPanelSections[sectionId] = section;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
173
web_view_searchpanel/static/src/scss/kanban_view.scss
Normal file
@@ -0,0 +1,173 @@
|
||||
/**********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
*
|
||||
* This file is part of MuK Search Panel
|
||||
* (see https://mukit.at).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
.o_kanban_with_searchpanel {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: flex-start;
|
||||
|
||||
.o_onboarding_container {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.o_kanban_view {
|
||||
flex: 1 1 calc(100% - #{$o-searchpanel-w});
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
.o_search_panel {
|
||||
flex: 0 0 $o-searchpanel-w;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
padding: $o-searchpanel-p-small $o-searchpanel-p-small $o-searchpanel-p*2 $o-searchpanel-p;
|
||||
border-right: 1px solid $gray-300;
|
||||
background-color: white;
|
||||
position: relative;
|
||||
|
||||
.o_search_panel_loading {
|
||||
position: absolute;
|
||||
margin-top: -30px;
|
||||
margin-left: -30px;
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
font-size: 60px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.o_search_panel_category .o_search_panel_section_icon {
|
||||
color: $o-searchpanel-category-default-color;
|
||||
}
|
||||
.o_search_panel_filter .o_search_panel_section_icon {
|
||||
color: $o-searchpanel-filter-default-color;
|
||||
}
|
||||
|
||||
.o_search_panel_label {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.o_toggle_fold {
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
.o_search_panel_section_header {
|
||||
padding: $o-searchpanel-p-small 0;
|
||||
}
|
||||
.list-group-item {
|
||||
padding: 0 0 $o-searchpanel-p-small 0;
|
||||
|
||||
.list-group-item {
|
||||
padding: 0 0 0 $custom-control-gutter;
|
||||
margin-bottom: $o-searchpanel-p-tiny*0.5;
|
||||
&:first-child {
|
||||
margin-top: $o-searchpanel-p-tiny*0.5;
|
||||
}
|
||||
}
|
||||
span.o_search_panel_label_title {
|
||||
color: $headings-color;
|
||||
@include o-text-overflow(inline-block, calc(100% - 22px));
|
||||
}
|
||||
header.active {
|
||||
background-color: $list-group-action-active-bg;
|
||||
}
|
||||
}
|
||||
.o_search_panel_category_value {
|
||||
header {
|
||||
margin-left: -$o-searchpanel-p-tiny;
|
||||
padding-left: $o-searchpanel-p-tiny;
|
||||
}
|
||||
.o_search_panel_category_value {
|
||||
position: relative;
|
||||
padding-left: $o-searchpanel-p;
|
||||
padding-bottom: $o-searchpanel-p-tiny;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:before, &:after {
|
||||
@include o-position-absolute(0, $left: $o-searchpanel-p-tiny)
|
||||
@include size(1px, 100%);
|
||||
background: $gray-500;
|
||||
content: '';
|
||||
}
|
||||
&:after {
|
||||
top: 10px;
|
||||
@include size(8px, 1px);
|
||||
}
|
||||
&:last-child {
|
||||
&:before {
|
||||
height: 11px;
|
||||
}
|
||||
&:after {
|
||||
top: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_kanban_with_searchpanel {
|
||||
flex-direction: column;
|
||||
|
||||
.o_onboarding_container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details.o_search_panel {
|
||||
flex-basis: auto;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
|
||||
> summary {
|
||||
padding: $o-searchpanel-p-small;
|
||||
// Hide the caret. For details see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary
|
||||
list-style-type: none;
|
||||
&::-webkit-details-marker {
|
||||
display: none
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
> .o_search_panel_section {
|
||||
margin: 0 $o-searchpanel-p-small 0 $o-searchpanel-p;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
z-index: $zindex-dropdown;
|
||||
|
||||
> summary {
|
||||
background-color: $list-group-action-active-bg;
|
||||
}
|
||||
|
||||
.fa-chevron-left:before {
|
||||
content: "\f078";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
web_view_searchpanel/static/src/scss/variables.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
/**********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
*
|
||||
* This file is part of MuK Search Panel
|
||||
* (see https://mukit.at).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
$o-searchpanel-w: 220px;
|
||||
|
||||
$o-searchpanel-p: $o-horizontal-padding;
|
||||
$o-searchpanel-p-small: $o-horizontal-padding*0.5;
|
||||
$o-searchpanel-p-tiny: $o-searchpanel-p-small*0.5;
|
||||
|
||||
$o-searchpanel-category-default-color: $o-brand-primary;
|
||||
$o-searchpanel-filter-default-color: $o-brand-odoo;
|
||||
149
web_view_searchpanel/static/src/xml/kanban.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
|
||||
This file is part of MuK Search Panel
|
||||
(see https://mukit.at).
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="SearchPanel.SectionHeader">
|
||||
<header class="o_search_panel_section_header text-uppercase">
|
||||
<i t-attf-class="mr-1 fa #{section.icon} o_search_panel_section_icon" t-attf-style="#{section.color ? ('color: ' + section.color) : ''}"/>
|
||||
<b><t t-esc="section.description"/></b>
|
||||
</header>
|
||||
</t>
|
||||
|
||||
<t t-name="SearchPanel.Category">
|
||||
<section class="o_search_panel_section o_search_panel_category">
|
||||
<t t-call="SearchPanel.SectionHeader">
|
||||
<t t-set="section" t-value="category"/>
|
||||
</t>
|
||||
<ul class="list-group d-block">
|
||||
<li class="o_search_panel_category_value border-0 list-group-item" t-att-data-category-id="category.id">
|
||||
<header t-att-class="'list-group-item-action' + (!category.activeValueId ? ' active' : '')">
|
||||
<label class="o_search_panel_label mb0 d-block">
|
||||
<span class="o_search_panel_label_title"><b>All</b></span>
|
||||
</label>
|
||||
</header>
|
||||
</li>
|
||||
<t t-call="SearchPanel.CategoryValues">
|
||||
<t t-set="values" t-value="category.rootIds"/>
|
||||
</t>
|
||||
</ul>
|
||||
</section>
|
||||
</t>
|
||||
<t t-name="SearchPanel.CategoryValues">
|
||||
<t t-foreach="values" t-as="valueId">
|
||||
<t t-set="value" t-value="category.values[valueId]"/>
|
||||
<li class="o_search_panel_category_value border-0 list-group-item" t-att-data-id="value.id" t-att-data-category-id="category.id">
|
||||
<header t-att-class="'list-group-item-action' + (value.id === category.activeValueId ? ' active' : '')">
|
||||
<label t-att-for="value.display_name" class="o_search_panel_label mb0 d-block">
|
||||
<i t-if="value.childrenIds.length" t-att-class="'fa fa-fw pull-right o_toggle_fold ' + (value.folded ? 'fa-caret-left' : 'fa-caret-down')"/>
|
||||
<span class="o_search_panel_label_title"><t t-esc="value.display_name"/></span>
|
||||
</label>
|
||||
</header>
|
||||
<ul t-if="!value.folded" class="list-group d-block">
|
||||
<t t-call="SearchPanel.CategoryValues">
|
||||
<t t-set="values" t-value="value.childrenIds"/>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="SearchPanel.Filter">
|
||||
<section class="o_search_panel_section o_search_panel_filter">
|
||||
<t t-call="SearchPanel.SectionHeader">
|
||||
<t t-set="section" t-value="filter"/>
|
||||
</t>
|
||||
<ul class="list-group d-block">
|
||||
<t t-if="filter.groups" t-call="SearchPanel.FilterGroups">
|
||||
<t t-set="groups" t-value="filter.groups"/>
|
||||
</t>
|
||||
<t t-else="" t-call="SearchPanel.FilterValues">
|
||||
<t t-set="sortedValueIds" t-value="filter.sortedValueIds"/>
|
||||
<t t-set="values" t-value="filter.values"/>
|
||||
</t>
|
||||
</ul>
|
||||
</section>
|
||||
</t>
|
||||
<t t-name="SearchPanel.FilterGroups">
|
||||
<li t-foreach="filter.sortedGroupIds" t-as="groupId" t-att-data-group-id="groupId" t-att-data-filter-id="filter.id"
|
||||
class="o_search_panel_filter_group list-group-item border-0">
|
||||
<t t-set="group" t-value="groups[groupId]"/>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<t t-set="inputId" t-value="_.uniqueId('input_')"/>
|
||||
<input type="checkbox" t-att-id="inputId" t-att-checked="group.state === 'checked' ? 'checked' : undefined" class="custom-control-input"/>
|
||||
<label t-att-for="inputId" class="o_search_panel_label custom-control-label d-block" t-att-title="group.tooltip">
|
||||
<i t-att-class="'fa fa-fw pull-right o_toggle_fold ' + (group.folded ? 'fa-caret-left' : 'fa-caret-down')"/>
|
||||
<span class="o_search_panel_label_title"><t t-esc="group.name"/></span>
|
||||
</label>
|
||||
</div>
|
||||
<ul t-if="!group.folded" class="list-group d-block">
|
||||
<t t-call="SearchPanel.FilterValues">
|
||||
<t t-set="sortedValueIds" t-value="group.sortedValueIds"/>
|
||||
<t t-set="values" t-value="group.values"/>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
<ul t-if="groups['false']" class="list-group d-block">
|
||||
<t t-call="SearchPanel.FilterValues">
|
||||
<t t-set="group" t-value="groups['false']"/>
|
||||
<t t-set="sortedValueIds" t-value="group.sortedValueIds"/>
|
||||
<t t-set="values" t-value="group.values"/>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
<t t-name="SearchPanel.FilterValues">
|
||||
<li t-foreach="sortedValueIds" t-as="valueId" t-att-data-value-id="valueId" t-att-data-filter-id="filter.id"
|
||||
class="o_search_panel_filter_value list-group-item border-0">
|
||||
<t t-set="value" t-value="values[valueId]"/>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<t t-set="inputId" t-value="_.uniqueId('input_')"/>
|
||||
<input type="checkbox" t-att-id="inputId" t-att-checked="value.checked ? 'checked' : undefined" class="custom-control-input"/>
|
||||
<label t-att-for="inputId" class="o_search_panel_label custom-control-label d-block" t-att-title="group && group.tooltip">
|
||||
<span class="o_search_panel_label_title"><t t-esc="value.name"/></span>
|
||||
<span t-if="value.count > 0" class="pull-right text-muted mr-2 mt-1 small"><t t-esc="value.count"/></span>
|
||||
<span t-if="filter.disableCounters" class="pull-right text-muted mr-2 mt-1 small">?</span>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<t t-name="SearchPanel.MobileSummary">
|
||||
<t t-set="emptySelection" t-value="!categories.length & !filterValues.length"/>
|
||||
<summary class="d-flex align-items-center">
|
||||
<div class="text-truncate font-italic ml-2 mr-auto">
|
||||
<t t-if="emptySelection">Filters...</t>
|
||||
<span t-foreach="categories" t-as="category" class="o_search_panel_category mr-1">
|
||||
<i t-if="category.icon" t-attf-class="o_search_panel_section_icon fa fa-w #{category.icon}" t-attf-style="#{category.color ? ('color: ' + category.color) : undefined}"/>
|
||||
<t t-esc="category.breadcrumb.join(separator)"/>
|
||||
</span>
|
||||
<span t-foreach="filterValues" t-as="filterValue" class="o_search_panel_filter mr-1">
|
||||
<i t-if="filterValue.icon" t-attf-class="o_search_panel_section_icon fa fa-w #{filterValue.icon}" t-attf-style="#{filterValue.color ? ('color: ' + filterValue.color) : undefined}"/>
|
||||
<t t-esc="filterValue.name"/>
|
||||
</span>
|
||||
</div>
|
||||
<i class="fa fa-fw fa-chevron-left"/>
|
||||
</summary>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
40
web_view_searchpanel/template/assets.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
Copyright (c) 2017-2019 MuK IT GmbH.
|
||||
|
||||
This file is part of MuK Search Panel
|
||||
(see https://mukit.at).
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
|
||||
<odoo>
|
||||
|
||||
<template id="assets_backend" name="Backend Assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/css" href="/muk_web_searchpanel/static/src/scss/variables.scss" />
|
||||
<link rel="stylesheet" type="text/css" href="/muk_web_searchpanel/static/src/scss/kanban_view.scss" />
|
||||
</xpath>
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/muk_web_searchpanel/static/src/js/kanban_searchpanel.js" />
|
||||
<script type="text/javascript" src="/muk_web_searchpanel/static/src/js/kanban_controller.js" />
|
||||
<script type="text/javascript" src="/muk_web_searchpanel/static/src/js/kanban_renderer.js" />
|
||||
<script type="text/javascript" src="/muk_web_searchpanel/static/src/js/kanban_view.js" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||