mirror of
https://github.com/OCA/web.git
synced 2025-02-22 13:21:25 +02:00
Merge pull request #706 from LasLabs/feature/10.0/SMD-263-mig-web_widget_slick
[ADD] web_widget_slick: Add module
This commit is contained in:
114
web_widget_slick/README.rst
Executable file
114
web_widget_slick/README.rst
Executable file
@@ -0,0 +1,114 @@
|
||||
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
|
||||
=====================
|
||||
Slick Carousel Widget
|
||||
=====================
|
||||
|
||||
This module provides a Slick Carousel widget for use in the Odoo backend web interface.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Default usage is on a One2many attachment field, as defined below::
|
||||
|
||||
class SlickExample(models.Model):
|
||||
_name = 'slick.example'
|
||||
_description = 'Slick Example Model'
|
||||
image_ids = fields.One2many(
|
||||
name='Images',
|
||||
comodel_name='ir.attachment',
|
||||
inverse_name='res_id',
|
||||
)
|
||||
|
||||
Assuming the above model, you would add a Slick Carousel on the
|
||||
``image_ids`` column by using the following field definition in the
|
||||
model's form view::
|
||||
|
||||
<field name="image_ids" widget="one2many_slick_images" options="{}"/>
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
The widget passes options directly through to Slick, so any `setting
|
||||
available to Slick`_ is available to the widget. Additional options
|
||||
specific to Odoo are:
|
||||
|
||||
+-----------------+--------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Name | Type | Default | Description |
|
||||
+=================+==============+=====================+=============================================================================================================================================================================+
|
||||
| ``fieldName`` | ``String`` | ``datas`` | Field to lookup on relation table. Defaults to ``datas``, which is the data field used in ``ir.attachment`` table. This would be used to define a custom attachment model |
|
||||
+-----------------+--------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``modelName`` | ``String`` | ``ir.attachment`` | Model of attachment relation. This would be used to define a custom attachment model instead of default ``ir.attachment`` |
|
||||
+-----------------+--------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
.. _setting available to Slick: http://kenwheeler.github.io/slick/#settings
|
||||
|
||||
Example Module
|
||||
--------------
|
||||
|
||||
An example implementation, for instructional purposes as well as convenient
|
||||
functional testing, is provided in the `web_widget_slick_example` module.
|
||||
|
||||
* Install `web_widget_slick_example`.
|
||||
* Activate Developer Mode.
|
||||
* Go to Settings / Technical / Slick, and open the record to view the widget.
|
||||
|
||||
To try out different Slick settings:
|
||||
|
||||
* Go to Settings/User Interface/Views and search for 'slick.example.view.form'.
|
||||
* Open the form view record.
|
||||
* Click the Edit button.
|
||||
* In the Architecture editor, find `options="{'slidesToShow': 2}`, and add
|
||||
any desired settings (separated by commas) inside the curly braces.
|
||||
* Save the changes and browse to the widget, as described above, to see the
|
||||
widget with the new settings in effect.
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/162/10.0
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* Adding / Deleting images from a carousel is not currently supported.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/web/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Dave Lasley <dave@laslabs.com>
|
||||
* Brent Hughes <brent.hughes@laslabs.com>
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
3
web_widget_slick/__init__.py
Normal file
3
web_widget_slick/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016-2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
25
web_widget_slick/__manifest__.py
Normal file
25
web_widget_slick/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016-2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
{
|
||||
"name": "Slick Carousel Widget",
|
||||
"summary": "Adds SlickJS slider widget for use as a carousel on Many2one"
|
||||
" attachment fields in backend form views.",
|
||||
"version": "10.0.1.0.0",
|
||||
"category": "Web",
|
||||
"website": "https://laslabs.com/",
|
||||
"author": "LasLabs, Odoo Community Association (OCA)",
|
||||
"license": "LGPL-3",
|
||||
"application": False,
|
||||
"installable": True,
|
||||
"depends": [
|
||||
"web",
|
||||
],
|
||||
"data": [
|
||||
"templates/assets.xml",
|
||||
],
|
||||
"qweb": [
|
||||
"static/src/xml/web_widget_slick.xml",
|
||||
],
|
||||
}
|
||||
BIN
web_widget_slick/static/description/icon.png
Normal file
BIN
web_widget_slick/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
79
web_widget_slick/static/description/icon.svg
Normal file
79
web_widget_slick/static/description/icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 45 KiB |
BIN
web_widget_slick/static/lib/slick/ajax-loader.gif
Executable file
BIN
web_widget_slick/static/lib/slick/ajax-loader.gif
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
172
web_widget_slick/static/lib/slick/slick-theme.less
Executable file
172
web_widget_slick/static/lib/slick/slick-theme.less
Executable file
@@ -0,0 +1,172 @@
|
||||
/* Copyright 2013-2016 Ken Wheeler
|
||||
* Version 1.7.1
|
||||
* License MIT (https://opensource.org/licenses/MIT) */
|
||||
|
||||
@charset "UTF-8";
|
||||
|
||||
// Default Variables
|
||||
|
||||
@slick-font-path: "./fonts/";
|
||||
@slick-font-family: "slick";
|
||||
@slick-loader-path: "./";
|
||||
@slick-arrow-color: white;
|
||||
@slick-dot-color: black;
|
||||
@slick-dot-color-active: @slick-dot-color;
|
||||
@slick-prev-character: "←";
|
||||
@slick-next-character: "→";
|
||||
@slick-dot-character: "•";
|
||||
@slick-dot-size: 6px;
|
||||
@slick-opacity-default: 0.75;
|
||||
@slick-opacity-on-hover: 1;
|
||||
@slick-opacity-not-active: 0.25;
|
||||
|
||||
/* Slider */
|
||||
.slick-loading .slick-list{
|
||||
background: #fff url('@{slick-loader-path}ajax-loader.gif') center center no-repeat;
|
||||
}
|
||||
|
||||
/* Arrows */
|
||||
.slick-prev,
|
||||
.slick-next {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
line-height: 0px;
|
||||
font-size: 0px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(0, -50%);
|
||||
-ms-transform: translate(0, -50%);
|
||||
transform: translate(0, -50%);
|
||||
padding: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
&:hover, &:focus {
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
&:before {
|
||||
opacity: @slick-opacity-on-hover;
|
||||
}
|
||||
}
|
||||
&.slick-disabled:before {
|
||||
opacity: @slick-opacity-not-active;
|
||||
}
|
||||
}
|
||||
|
||||
.slick-prev:before, .slick-next:before {
|
||||
font-family: @slick-font-family;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
color: @slick-arrow-color;
|
||||
opacity: @slick-opacity-default;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
& when ( @slick-font-family = 'slick' ) {
|
||||
/* Icons */
|
||||
@font-face {
|
||||
font-family: 'slick';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
src: url('@{slick-font-path}slick.eot');
|
||||
src: url('@{slick-font-path}slick.eot?#iefix') format('embedded-opentype'), url('@{slick-font-path}slick.woff') format('woff'), url('@{slick-font-path}slick.ttf') format('truetype'), url('@{slick-font-path}slick.svg#slick') format('svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slick-prev {
|
||||
left: -25px;
|
||||
[dir="rtl"] & {
|
||||
left: auto;
|
||||
right: -25px;
|
||||
}
|
||||
&:before {
|
||||
content: @slick-prev-character;
|
||||
[dir="rtl"] & {
|
||||
content: @slick-next-character;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slick-next {
|
||||
right: -25px;
|
||||
[dir="rtl"] & {
|
||||
left: -25px;
|
||||
right: auto;
|
||||
}
|
||||
&:before {
|
||||
content: @slick-next-character;
|
||||
[dir="rtl"] & {
|
||||
content: @slick-prev-character;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dots */
|
||||
|
||||
.slick-dotted .slick-slider {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.slick-dots {
|
||||
position: absolute;
|
||||
bottom: -25px;
|
||||
list-style: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
li {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin: 0 5px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
outline: none;
|
||||
line-height: 0px;
|
||||
font-size: 0px;
|
||||
color: transparent;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
&:hover, &:focus {
|
||||
outline: none;
|
||||
&:before {
|
||||
opacity: @slick-opacity-on-hover;
|
||||
}
|
||||
}
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: @slick-dot-character;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-family: @slick-font-family;
|
||||
font-size: @slick-dot-size;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
color: @slick-dot-color;
|
||||
opacity: @slick-opacity-not-active;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
&.slick-active button:before {
|
||||
color: @slick-dot-color-active;
|
||||
opacity: @slick-opacity-default;
|
||||
}
|
||||
}
|
||||
}
|
||||
2996
web_widget_slick/static/lib/slick/slick.js
Executable file
2996
web_widget_slick/static/lib/slick/slick.js
Executable file
File diff suppressed because it is too large
Load Diff
104
web_widget_slick/static/lib/slick/slick.less
Executable file
104
web_widget_slick/static/lib/slick/slick.less
Executable file
@@ -0,0 +1,104 @@
|
||||
/* Copyright 2013-2016 Ken Wheeler
|
||||
* Version 1.7.1
|
||||
* License MIT (https://opensource.org/licenses/MIT) */
|
||||
|
||||
/* Slider */
|
||||
|
||||
.slick-slider {
|
||||
position: relative;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-ms-touch-action: pan-y;
|
||||
touch-action: pan-y;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.slick-list {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.dragging {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
}
|
||||
.slick-slider .slick-track,
|
||||
.slick-slider .slick-list {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-moz-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
-o-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.slick-track {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
|
||||
&:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.slick-loading & {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.slick-slide {
|
||||
float: left;
|
||||
height: 100%;
|
||||
min-height: 1px;
|
||||
[dir="rtl"] & {
|
||||
float: right;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
&.slick-loading img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
display: none;
|
||||
|
||||
&.dragging img {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.slick-initialized & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slick-loading & {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.slick-vertical & {
|
||||
display: block;
|
||||
height: auto;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
.slick-arrow.slick-hidden {
|
||||
display: none;
|
||||
}
|
||||
172
web_widget_slick/static/src/js/web_widget_slick.js
Normal file
172
web_widget_slick/static/src/js/web_widget_slick.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/* Copyright 2016-2017 LasLabs Inc.
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||
|
||||
odoo.define('web_widget_slick', function(require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var AbstractManyField = require('web.form_relational').AbstractManyField;
|
||||
|
||||
var FieldSlickImages = AbstractManyField.extend({
|
||||
|
||||
widget_class: 'o_slick',
|
||||
template: 'FieldSlickImages',
|
||||
$slick: null,
|
||||
no_rerender: true,
|
||||
loading: [],
|
||||
loaded: 0,
|
||||
|
||||
events: {
|
||||
'mousedown img': function(ev) {
|
||||
ev.preventDefault();
|
||||
},
|
||||
'touchstart img': function(ev) {
|
||||
ev.preventDefault();
|
||||
},
|
||||
// Triggering a resize on the lazyLoaded event prevents the carousel
|
||||
// from appearing empty when page loads
|
||||
'lazyLoaded': function(ev) {
|
||||
$(ev.target).trigger('resize');
|
||||
}
|
||||
},
|
||||
|
||||
defaults: {
|
||||
lazyLoad: 'ondemand',
|
||||
fieldName: 'datas',
|
||||
modelName: 'ir.attachment',
|
||||
slidesToShow: 3,
|
||||
slidesToScroll: 1,
|
||||
swipeToSlide: true,
|
||||
dots: true,
|
||||
infinite: true,
|
||||
speed: 500,
|
||||
arrows: true,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1024,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: 600,
|
||||
settings: {
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1
|
||||
}
|
||||
}
|
||||
// You can unslick at a given breakpoint now by adding:
|
||||
// settings: "unslick"
|
||||
// instead of a settings object
|
||||
]
|
||||
},
|
||||
|
||||
init: function(field_manager, node) {
|
||||
this._super(field_manager, node);
|
||||
this.options = _.defaults(this.options, this.defaults);
|
||||
},
|
||||
|
||||
destroy_content: function() {
|
||||
if (this.$slick) {
|
||||
var $imgs = this.$el.find('img');
|
||||
// Unslicking removes the carousel but re-appends any images,
|
||||
// so removal of images is also required
|
||||
$imgs.each($.proxy(this._slickRemove, this));
|
||||
this.$slick.slick('unslick');
|
||||
}
|
||||
},
|
||||
|
||||
render_value: function() {
|
||||
this._super();
|
||||
this.destroy_content();
|
||||
|
||||
this.$el.parent('td').addClass('o_slick_cell');
|
||||
this.$slick = $('<div class="slick-container"></div>');
|
||||
if (this.options.arrows) {
|
||||
this.$slick.addClass('slick-arrowed');
|
||||
}
|
||||
this.$el.append(this.$slick);
|
||||
|
||||
var baseUrl = '/web/image/' + this.options.modelName + '/';
|
||||
var value = this.get('value');
|
||||
this.loading.push.apply(value);
|
||||
_.each(value, $.proxy(this._slickRender, this, [baseUrl]));
|
||||
|
||||
this.$slick.slick(this.options);
|
||||
core.bus.on('resize', this, this._resizeCarousel);
|
||||
},
|
||||
|
||||
_resizeCarousel: function () {
|
||||
var maxWidth = this._resizeMaxWidth();
|
||||
var containerWidth = maxWidth;
|
||||
|
||||
var $parentCell = this.$el.parent('td');
|
||||
if ($parentCell.length) {
|
||||
var scaledWidth = this._resizeScaledWidth($parentCell, maxWidth);
|
||||
var labelWidth = this._resizeLabelWidth($parentCell);
|
||||
containerWidth = scaledWidth - labelWidth;
|
||||
}
|
||||
|
||||
var marginWidth = this._resizeMarginWidth(this.$slick);
|
||||
var carouselWidth = containerWidth - marginWidth;
|
||||
|
||||
// Set outerWidth of carousel, with minimum size. Minimum size can cause
|
||||
// overflow in some cases but prevents displaying with zero width
|
||||
this.$slick.outerWidth(Math.max(carouselWidth, 150));
|
||||
},
|
||||
|
||||
_resizeLabelWidth: function ($parentCell) {
|
||||
// If the widget has a label, subtract label cell's width, plus the extra
|
||||
// padding applied to the parent cell, from container width
|
||||
var $labelCell = $parentCell.prev('.o_td_label');
|
||||
if ($labelCell.length) {
|
||||
var parentPadding = $parentCell.outerWidth() - $parentCell.width();
|
||||
return $labelCell.outerWidth() + parentPadding;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
_resizeMarginWidth: function ($element) {
|
||||
// Subtract container's margins so outerWidth can be set properly
|
||||
return $element.outerWidth(true) - $element.outerWidth();
|
||||
},
|
||||
|
||||
_resizeMaxWidth: function () {
|
||||
// Determine the maximum possible width the widget container can occupy
|
||||
var parentSelectors = ['.o_form_sheet', '.o_form_nosheet'];
|
||||
var containerWidth = parentSelectors.map(function (selector) {
|
||||
return this.$el.closest(selector).width();
|
||||
}, this).filter(function (width) {
|
||||
return width !== null;
|
||||
})[0];
|
||||
|
||||
return containerWidth;
|
||||
},
|
||||
|
||||
_resizeScaledWidth: function ($parentCell, maxWidth) {
|
||||
// If the widget is inside a group tag, scale carousel size based on
|
||||
// intended % width of parent cell
|
||||
return maxWidth * parseInt($parentCell[0].style.width, 10) / 100;
|
||||
},
|
||||
|
||||
_slickRemove: function (idx, val) {
|
||||
this.$slick.slick('slickRemove', idx);
|
||||
},
|
||||
|
||||
_slickRender: function (baseUrl, id) {
|
||||
var $img = $('<img class="img img-responsive"></img>');
|
||||
var $div = $('<div></div>');
|
||||
$img.attr('data-lazy', baseUrl + id + '/' + this.options.fieldName);
|
||||
$div.append($img);
|
||||
this.$slick.append($div);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
core.form_widget_registry.add("one2many_slick_images", FieldSlickImages);
|
||||
|
||||
return {FieldSlickImages: FieldSlickImages};
|
||||
|
||||
});
|
||||
74
web_widget_slick/static/src/less/slick.less
Executable file
74
web_widget_slick/static/src/less/slick.less
Executable file
@@ -0,0 +1,74 @@
|
||||
/* Copyright 2016-2017 LasLabs Inc.
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||
|
||||
@slick-arrow-margin: 40px;
|
||||
@slick-arrow-offset: calc(-(@slick-arrow-margin - 5px));
|
||||
@slick-font-family: "FontAwesome";
|
||||
@slick-dot-character: "\f10c";
|
||||
@slick-dot-character-active: "\f111";
|
||||
@slick-dot-color: @odoo-brand-primary;
|
||||
@slick-dot-offset: 35px;
|
||||
@slick-dot-size: 12px;
|
||||
@slick-next-character: "\f054";
|
||||
@slick-prev-character: "\f053";
|
||||
@slick-opacity-default: 1;
|
||||
@slick-opacity-not-active: 0.5;
|
||||
|
||||
/* Odoo field */
|
||||
|
||||
.o_slick {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
|
||||
.slick-container {
|
||||
&.slick-dotted {
|
||||
margin-bottom: @slick-dot-offset;
|
||||
}
|
||||
&.slick-arrowed {
|
||||
margin-left: @slick-arrow-margin;
|
||||
margin-right: @slick-arrow-margin;
|
||||
.slick-prev {
|
||||
left: @slick-arrow-offset;
|
||||
}
|
||||
.slick-next {
|
||||
right: @slick-arrow-offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Images */
|
||||
|
||||
.slick-slide {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
outline: none;
|
||||
img {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Arrows */
|
||||
|
||||
.slick-arrow {
|
||||
height: 35px;
|
||||
width: 30px;
|
||||
font-size: 0 !important;
|
||||
padding: 0 !important;
|
||||
.btn();
|
||||
.btn-sm();
|
||||
.btn-primary();
|
||||
}
|
||||
|
||||
/* Dots */
|
||||
|
||||
.slick-dots {
|
||||
bottom: calc(-(@slick-dot-offset));
|
||||
li {
|
||||
&.slick-active button:before {
|
||||
content: @slick-dot-character-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
web_widget_slick/static/src/xml/web_widget_slick.xml
Normal file
9
web_widget_slick/static/src/xml/web_widget_slick.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2016-2017 LasLabs Inc.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -->
|
||||
|
||||
<template>
|
||||
<t t-name="FieldSlickImages">
|
||||
<div t-attf-class="o_form_field {{ widget.widget_class }}"></div>
|
||||
</t>
|
||||
</template>
|
||||
225
web_widget_slick/static/tests/js/web_widget_slick.js
Normal file
225
web_widget_slick/static/tests/js/web_widget_slick.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/* Copyright 2017 LasLabs Inc.
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||
|
||||
odoo.define_section('web_widget_slick', ['web.core', 'web.form_common'], function(test) {
|
||||
"use strict";
|
||||
|
||||
function appendWidget (core, formCommon, $fix) {
|
||||
var fieldManager = new formCommon.DefaultFieldManager(null, {});
|
||||
var node = {'attrs': {}};
|
||||
var FieldSlickImages = core.form_widget_registry.get('one2many_slick_images');
|
||||
var widget = new FieldSlickImages(fieldManager, node);
|
||||
widget.appendTo($fix);
|
||||
return widget;
|
||||
}
|
||||
|
||||
function imageUrl (modelName, fieldName, id) {
|
||||
return '/web/image/' + modelName + '/' + id + '/' + fieldName;
|
||||
}
|
||||
|
||||
test('It should add a slick widget',
|
||||
function(assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var slickContainerCount = $fix.find('.slick-container').length;
|
||||
assert.strictEqual(slickContainerCount, 1);
|
||||
}
|
||||
);
|
||||
|
||||
test('.init() should add defaults to options',
|
||||
function(assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var fieldManager = new formCommon.DefaultFieldManager(null, {});
|
||||
var node = {'attrs': {}};
|
||||
var FieldSlickImages = core.form_widget_registry.get('one2many_slick_images');
|
||||
var widget = new FieldSlickImages(fieldManager, node);
|
||||
widget.appendTo($fix);
|
||||
|
||||
widget.defaults.testing = 'tested';
|
||||
widget.init(fieldManager, node);
|
||||
assert.strictEqual(widget.options.testing, 'tested');
|
||||
}
|
||||
);
|
||||
|
||||
test('.destroy_content() should remove images',
|
||||
function(assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var $slickImage = $('<div><img></div>');
|
||||
widget.$slick.slick('slickAdd', $slickImage);
|
||||
widget.destroy_content();
|
||||
|
||||
var slickImageCount = widget.$slick.find('img').length;
|
||||
assert.strictEqual(slickImageCount, 0);
|
||||
}
|
||||
);
|
||||
|
||||
test('.destroy_content() should remove carousel',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
widget.destroy_content();
|
||||
|
||||
var slickChildren = widget.$slick.children().length;
|
||||
assert.strictEqual(slickChildren, 0);
|
||||
}
|
||||
);
|
||||
|
||||
test('.render_value() should add images corresponding to field value',
|
||||
function(assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var fieldValues = [1, 2];
|
||||
widget.set({'value': fieldValues});
|
||||
widget.render_value();
|
||||
|
||||
var slickImages = widget.$slick.find('img');
|
||||
var slickImageUrls = slickImages.map(function() {
|
||||
return $(this).data('lazy');
|
||||
}).get();
|
||||
|
||||
var modelName = widget.options.modelName;
|
||||
var fieldName = widget.options.fieldName;
|
||||
var expectedUrls = fieldValues.map(function(id) {
|
||||
return '/web/image/' + modelName + '/' + id + '/' + fieldName;
|
||||
});
|
||||
|
||||
assert.deepEqual(slickImageUrls, expectedUrls);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeCarousel() should resize the widget',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var $nosheet = $('<div class="o_form_nosheet"></div>');
|
||||
$fix.append($nosheet);
|
||||
|
||||
var widget = appendWidget(core, formCommon, $nosheet);
|
||||
var setWidth = 50;
|
||||
widget.$slick.outerWidth(setWidth);
|
||||
|
||||
widget._resizeCarousel();
|
||||
|
||||
assert.notStrictEqual(widget.$slick.outerWidth(), setWidth);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeCarousel() should be called when container is resized',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var $nosheet = $('<div class="o_form_nosheet"></div>');
|
||||
$fix.append($nosheet);
|
||||
|
||||
var widget = appendWidget(core, formCommon, $nosheet);
|
||||
var setWidth = 50;
|
||||
widget.$slick.outerWidth(setWidth);
|
||||
|
||||
core.bus.trigger('resize');
|
||||
|
||||
assert.notStrictEqual(widget.$slick.outerWidth(), setWidth);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeLabelWidth() should return the width of the preceding ' +
|
||||
'sibling label cell if it exists',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var width = 100;
|
||||
var $cell = $('<td style="width:10px;"></td>');
|
||||
var $labelCell = $('<td class="o_td_label"></td>');
|
||||
$labelCell.outerWidth(width);
|
||||
|
||||
widget.$slick.append($labelCell);
|
||||
widget.$slick.append($cell);
|
||||
|
||||
assert.strictEqual(widget._resizeLabelWidth($cell), width);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeLabelWidth() should return 0 if the previous sibling cell ' +
|
||||
' of the provided element is not a label cell',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var width = '100px';
|
||||
var $cell = $('<td></td>');
|
||||
widget.$slick.append($('<td style="width:' + width + ';"></td>'));
|
||||
widget.$slick.append($cell);
|
||||
|
||||
assert.strictEqual(widget._resizeLabelWidth($cell), 0);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeMarginWidth() should return the total left and right ' +
|
||||
' margins of the provided element',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var elementStyle = 'margin-left: 12px; margin-right: 7px;';
|
||||
var marginTotal = 19;
|
||||
var $element = $('<div style="' + elementStyle + '"></div>');
|
||||
widget.$slick.append($element);
|
||||
|
||||
assert.strictEqual(widget._resizeMarginWidth($element), marginTotal);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeMaxWidth() should return the width of the closest sheet element',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var $sheet1 = $('<div class="o_form_sheet"></div>');
|
||||
var $sheet2 = $('<div class="o_form_sheet"></div>');
|
||||
var expectedWidth = 266;
|
||||
|
||||
$sheet1.width(700);
|
||||
$sheet2.width(expectedWidth);
|
||||
$sheet1.append($sheet2);
|
||||
$fix.append($sheet1);
|
||||
|
||||
var widget = appendWidget(core, formCommon, $sheet2);
|
||||
|
||||
assert.strictEqual(widget._resizeMaxWidth(), expectedWidth);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeMaxWidth() should return the width of the closest nosheet element',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var $nosheet1 = $('<div class="o_form_nosheet"></div>');
|
||||
var $nosheet2 = $('<div class="o_form_nosheet"></div>');
|
||||
var expectedWidth = 266;
|
||||
|
||||
$nosheet1.width(700);
|
||||
$nosheet2.width(expectedWidth);
|
||||
$nosheet1.append($nosheet2);
|
||||
$fix.append($nosheet1);
|
||||
|
||||
var widget = appendWidget(core, formCommon, $nosheet2);
|
||||
|
||||
assert.strictEqual(widget._resizeMaxWidth(), expectedWidth);
|
||||
}
|
||||
);
|
||||
|
||||
test('._resizeScaledWidth() should return the provided integer, scaled' +
|
||||
'to the % width in the provided element style attribute',
|
||||
function (assert, core, formCommon) {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var widget = appendWidget(core, formCommon, $fix);
|
||||
|
||||
var givenWidth = 100;
|
||||
var widthPercent = 54;
|
||||
var expectedWidth = widthPercent;
|
||||
var $cell = $('<td style="width:' + widthPercent + '%;"></td>');
|
||||
|
||||
assert.strictEqual(widget._resizeScaledWidth($cell, givenWidth), expectedWidth);
|
||||
}
|
||||
);
|
||||
});
|
||||
36
web_widget_slick/templates/assets.xml
Normal file
36
web_widget_slick/templates/assets.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2016-2017 LasLabs Inc.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<odoo>
|
||||
<template id="assets_slick" inherit_id="web.assets_backend">
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<link rel="stylesheet"
|
||||
type="text/less"
|
||||
href="/web_widget_slick/static/lib/slick/slick.less"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
type="text/less"
|
||||
href="/web_widget_slick/static/lib/slick/slick-theme.less"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
type="text/less"
|
||||
href="/web_widget_slick/static/src/less/slick.less"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_widget_slick/static/lib/slick/slick.js"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_widget_slick/static/src/js/web_widget_slick.js"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="application/javascript"
|
||||
src="/web_widget_slick/static/tests/js/web_widget_slick.js"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
4
web_widget_slick/tests/__init__.py
Normal file
4
web_widget_slick/tests/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import test_ui
|
||||
19
web_widget_slick/tests/test_ui.py
Normal file
19
web_widget_slick/tests/test_ui.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
|
||||
class UICase(HttpCase):
|
||||
|
||||
post_install = True
|
||||
at_install = False
|
||||
|
||||
def test_ui_web(self):
|
||||
"""Test backend tests."""
|
||||
self.phantom_js(
|
||||
"/web/tests?debug=assets&module=web_widget_slick",
|
||||
"",
|
||||
login="admin",
|
||||
)
|
||||
48
web_widget_slick_example/README.rst
Executable file
48
web_widget_slick_example/README.rst
Executable file
@@ -0,0 +1,48 @@
|
||||
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
|
||||
=============================
|
||||
Slick Carousel Widget Example
|
||||
=============================
|
||||
|
||||
This module provides an example of how to implement the web_widget_slick module.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/web/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Dave Lasley <dave@laslabs.com>
|
||||
* Brent Hughes <brent.hughes@laslabs.com>
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
5
web_widget_slick_example/__init__.py
Executable file
5
web_widget_slick_example/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016-2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import models
|
||||
25
web_widget_slick_example/__manifest__.py
Executable file
25
web_widget_slick_example/__manifest__.py
Executable file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016-2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
{
|
||||
"name": "Slick Carousel Widget Example",
|
||||
"summary": "Example usage of the web_widget_slick module",
|
||||
"version": "10.0.1.0.0",
|
||||
"category": "Hidden",
|
||||
"website": "https://laslabs.com/",
|
||||
"author": "LasLabs, Odoo Community Association (OCA)",
|
||||
"license": "LGPL-3",
|
||||
"application": False,
|
||||
"installable": True,
|
||||
"depends": [
|
||||
"web_widget_slick",
|
||||
],
|
||||
"data": [
|
||||
'views/slick_example_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
"demo": [
|
||||
'demo/slick_example_data.xml',
|
||||
],
|
||||
}
|
||||
34
web_widget_slick_example/demo/slick_example_data.xml
Normal file
34
web_widget_slick_example/demo/slick_example_data.xml
Normal file
File diff suppressed because one or more lines are too long
5
web_widget_slick_example/models/__init__.py
Executable file
5
web_widget_slick_example/models/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016-2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import slick_example
|
||||
16
web_widget_slick_example/models/slick_example.py
Executable file
16
web_widget_slick_example/models/slick_example.py
Executable file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016-2017 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class SlickExample(models.Model):
|
||||
_name = 'slick.example'
|
||||
_description = 'Slick Example Model'
|
||||
|
||||
image_ids = fields.One2many(
|
||||
name='Images',
|
||||
comodel_name='ir.attachment',
|
||||
inverse_name='res_id',
|
||||
)
|
||||
2
web_widget_slick_example/security/ir.model.access.csv
Normal file
2
web_widget_slick_example/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
slick_example_manager,slick.example.manager,web_widget_slick_example.model_slick_example,base.group_no_one,1,1,1,1
|
||||
|
BIN
web_widget_slick_example/static/description/icon.png
Normal file
BIN
web_widget_slick_example/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
79
web_widget_slick_example/static/description/icon.svg
Normal file
79
web_widget_slick_example/static/description/icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 45 KiB |
46
web_widget_slick_example/views/slick_example_view.xml
Normal file
46
web_widget_slick_example/views/slick_example_view.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2016-2017 LasLabs Inc.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -->
|
||||
|
||||
<odoo>
|
||||
<record id="slick_example_view_form" model="ir.ui.view">
|
||||
<field name="name">slick.example.view.form</field>
|
||||
<field name="model">slick.example</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Slick Example">
|
||||
<header />
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="image_ids" widget="one2many_slick_images"
|
||||
options="{'slidesToShow': 2}" />
|
||||
</group>
|
||||
</sheet>
|
||||
<footer />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slick_example_view_tree" model="ir.ui.view">
|
||||
<field name="name">slick.example.view.tree</field>
|
||||
<field name="model">slick.example</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Slick Example">
|
||||
<field name="id" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slick_example_action" model="ir.actions.act_window">
|
||||
<field name="name">Slick Examples</field>
|
||||
<field name="res_model">slick.example</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="slick_example_menu"
|
||||
name="Slick"
|
||||
parent="base.menu_custom"
|
||||
action="slick_example_action"
|
||||
sequence="1" />
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user