mirror of https://github.com/OCA/web.git
[ADD] web_widget_slickroom: Create module
* Extend web_widget_slick widget to open web_widget_darkroom modal and update selected carousel image on close * Override _slickRender method from web_widget_slick to add data attribute w/ record id * Change cursor to pointer on slickroom images * Disable features in read-only mode * Keep default cursor * Prevent modal from opening * Add tests [IMP] web_widget_slickroom: Make requested changes * Use jquery .data instead of .attr for image record IDs * Add explicit img selector when finding images by record ID * Remove svg version of iconpull/853/head
parent
9298796eef
commit
6cbd754f51
|
@ -0,0 +1,94 @@
|
||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:target: http://www.gnu.org/licenses/agpl
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
===================================================
|
||||||
|
Slick Carousel Widget with DarkroomJS Image Editing
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
This module extends the `Slick`_ carousel widget provided by
|
||||||
|
`web_widget_slick` to include the `DarkroomJS`_ image editing features provided
|
||||||
|
by `web_widget_darkroom`.
|
||||||
|
|
||||||
|
.. _Slick: http://kenwheeler.github.io/slick
|
||||||
|
.. _DarkroomJS: https://github.com/MattKetmo/darkroomjs
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
To create a Slick carousel widget with DarkroomJS support, follow the
|
||||||
|
usage instructions in the `web_widget_slick` documentation, but replace
|
||||||
|
"one2many_slick_images" with "slickroom" in the field definition, as shown
|
||||||
|
here::
|
||||||
|
|
||||||
|
<field name="image_ids" widget="slickroom" options="{}"/>
|
||||||
|
|
||||||
|
To edit an image in a carousel, simply click the Edit button in the form view,
|
||||||
|
then click on the image you wish to edit to open a DarkroomJS modal. Edit the
|
||||||
|
image as desired according to the `web_widget_darkroom` documentation, and
|
||||||
|
click Save to save the changes and update the carousel.
|
||||||
|
|
||||||
|
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.
|
||||||
|
* The standard Slick carousel widget (from `web_widget_slick`) is displayed on
|
||||||
|
top, followed by the slickroom widget with DarkroomJS support. Click the Edit
|
||||||
|
button in the form view to try out the DarkroomJS features.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
------------
|
||||||
|
|
||||||
|
* 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.
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2017 LasLabs Inc.
|
||||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2017 LasLabs Inc.
|
||||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Slick Carousel Widget with DarkroomJS Image Editing",
|
||||||
|
"summary": "Provides Slick Carousel Widget with DarkroomJS image editing",
|
||||||
|
"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_widget_darkroom",
|
||||||
|
"web_widget_slick",
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
"templates/assets.xml",
|
||||||
|
],
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
|
@ -0,0 +1,67 @@
|
||||||
|
/* Copyright 2017 LasLabs Inc.
|
||||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||||
|
|
||||||
|
odoo.define('web_widget_slickroom', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var core = require('web.core');
|
||||||
|
var FieldSlickImages = require('web_widget_slick').FieldSlickImages;
|
||||||
|
|
||||||
|
var FieldSlickroomImages = FieldSlickImages.extend({
|
||||||
|
|
||||||
|
widget_class: FieldSlickImages.prototype.widget_class + ' o_slickroom',
|
||||||
|
events: _.extend({}, FieldSlickImages.prototype.events, {
|
||||||
|
'click img': '_openModal'
|
||||||
|
}),
|
||||||
|
|
||||||
|
_openModal: function (ev) {
|
||||||
|
if (this.get("effective_readonly")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordId = $(ev.target).data('record-id');
|
||||||
|
var modalAction = {
|
||||||
|
type: 'ir.actions.act_window',
|
||||||
|
res_model: 'darkroom.modal',
|
||||||
|
name: 'Darkroom',
|
||||||
|
views: [[false, 'form']],
|
||||||
|
target: 'new',
|
||||||
|
context: {
|
||||||
|
active_field: this.options.fieldName,
|
||||||
|
active_model: this.options.modelName,
|
||||||
|
active_record_id: recordId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.do_action(modalAction, {
|
||||||
|
on_close: $.proxy(this._updateImage, this, recordId)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_slickRender: function(baseUrl, id) {
|
||||||
|
this._super(baseUrl, id);
|
||||||
|
this.$slick.find('img:last').data('record-id', id);
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateImage: function (recordId) {
|
||||||
|
// SlickJS creates 'clones', so multiple elements need updated src
|
||||||
|
var $imgs = this.$slick.find('img').filter(function () {
|
||||||
|
return $(this).data('record-id') === recordId;
|
||||||
|
});
|
||||||
|
var $loaded = $imgs.filter('[src]');
|
||||||
|
var $notLoaded = $imgs.filter('[data-lazy]');
|
||||||
|
|
||||||
|
var imgUrl = $loaded.first().attr('src');
|
||||||
|
var imgUrlNew = imgUrl + "?unique=" + new Date().getTime();
|
||||||
|
|
||||||
|
$loaded.attr('src', imgUrlNew);
|
||||||
|
$notLoaded.attr('data-lazy', imgUrlNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
core.form_widget_registry.add("slickroom", FieldSlickroomImages);
|
||||||
|
|
||||||
|
return {FieldSlickroomImages: FieldSlickroomImages};
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* Copyright 2017 LasLabs Inc.
|
||||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||||
|
|
||||||
|
.o_form_editable .o_slickroom img {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
/* Copyright 2017 LasLabs Inc.
|
||||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). */
|
||||||
|
|
||||||
|
odoo.define_section('web_widget_slickroom', ['web.core', 'web.form_common'], function (test) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function appendWidget (core, formCommon, $fix) {
|
||||||
|
var fieldManager = new formCommon.DefaultFieldManager(null, {});
|
||||||
|
var node = {'attrs': {}};
|
||||||
|
var FieldSlickroomImages = core.form_widget_registry.get('slickroom');
|
||||||
|
var widget = new FieldSlickroomImages(fieldManager, node);
|
||||||
|
widget.appendTo($fix);
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
function imgHTML (id, attr) {
|
||||||
|
return $(
|
||||||
|
'<div><img data-record-id="' +id + '" ' + attr +
|
||||||
|
'="/web/image/ir.attachment/' + id + '/datas"></div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('._openModal() should open a darkroom modal with provided options',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
var recordId = 1;
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(recordId, 'src'));
|
||||||
|
|
||||||
|
var modalAction = {};
|
||||||
|
widget.do_action = function (action, options) {
|
||||||
|
modalAction = action;
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedAction = {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": "darkroom.modal",
|
||||||
|
"name": "Darkroom",
|
||||||
|
"views": [[false, "form"]],
|
||||||
|
"target": "new",
|
||||||
|
"context": {
|
||||||
|
"active_field": widget.options.fieldName,
|
||||||
|
"active_model": widget.options.modelName,
|
||||||
|
"active_record_id": recordId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.$('img').click();
|
||||||
|
|
||||||
|
assert.deepEqual(modalAction, expectedAction);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('._openModal() should open a darkroom modal with on_close action ' +
|
||||||
|
'that calls ._updateImage()',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
var recordId = 1;
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(recordId, 'src'));
|
||||||
|
|
||||||
|
var modalOptions = {};
|
||||||
|
widget.do_action = function (action, options) {
|
||||||
|
modalOptions = options;
|
||||||
|
};
|
||||||
|
|
||||||
|
var $img = widget.$('img');
|
||||||
|
$img.click();
|
||||||
|
modalOptions.on_close();
|
||||||
|
|
||||||
|
assert.notStrictEqual($img.attr('src').indexOf('?unique'), -1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('._slickRender() should add data-record-id to images',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
|
||||||
|
var values = [1, 2, 3];
|
||||||
|
|
||||||
|
_.each(values, function(recordId) {
|
||||||
|
widget._slickRender('/web/image/ir.attachments/', recordId);
|
||||||
|
});
|
||||||
|
|
||||||
|
var slickImageIds = widget.$slick.find('img').map(function () {
|
||||||
|
return $(this).data('record-id');
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
assert.deepEqual(slickImageIds, values);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('._updateImage() should update source of matching/loaded slick images',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
var imgId = 1;
|
||||||
|
|
||||||
|
for(var i = 0; i < 5; i++) {
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(imgId, 'src'));
|
||||||
|
}
|
||||||
|
|
||||||
|
widget._updateImage(imgId);
|
||||||
|
|
||||||
|
var $matches = widget.$slick.find(
|
||||||
|
'[data-record-id="' + imgId + '"]'
|
||||||
|
);
|
||||||
|
$matches.each(function () {
|
||||||
|
var newSrc = $(this).attr('src');
|
||||||
|
assert.notStrictEqual(newSrc.indexOf('?unique'), -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('._updateImage() should update lazy data attribute of matching/unloaded slick images',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
var imgId = 1;
|
||||||
|
|
||||||
|
for(var i = 0; i < 5; i++) {
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(imgId, 'data-lazy'));
|
||||||
|
}
|
||||||
|
|
||||||
|
widget._updateImage(1);
|
||||||
|
|
||||||
|
var $matches = widget.$slick.find(
|
||||||
|
'[data-record-id="' + imgId + '"]'
|
||||||
|
);
|
||||||
|
$matches.each(function () {
|
||||||
|
var newSrc = $(this).attr('data-lazy');
|
||||||
|
assert.notStrictEqual(newSrc.indexOf('?unique'), -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('._updateImage() should not update source of non-matching/loaded slick images',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
var imgId = 1;
|
||||||
|
var img2Id = 2;
|
||||||
|
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(imgId, 'src'));
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(img2Id, 'src'));
|
||||||
|
|
||||||
|
widget._updateImage(1);
|
||||||
|
|
||||||
|
var $notMatch = widget.$slick.find(
|
||||||
|
'[data-record-id="' + img2Id + '"]'
|
||||||
|
);
|
||||||
|
assert.strictEqual($notMatch.attr('src').indexOf('?unique'), -1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('._updateImage() should not update lazy data attribute of ' +
|
||||||
|
'non-matching/unloaded slick images',
|
||||||
|
function (assert, core, formCommon) {
|
||||||
|
var $fix = $('#qunit-fixture');
|
||||||
|
var widget = appendWidget(core, formCommon, $fix);
|
||||||
|
var imgId = 1;
|
||||||
|
var img2Id = 2;
|
||||||
|
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(imgId, 'data-lazy'));
|
||||||
|
widget.$slick.slick('slickAdd', imgHTML(img2Id, 'data-lazy'));
|
||||||
|
|
||||||
|
widget._updateImage(1);
|
||||||
|
|
||||||
|
var $notMatch = widget.$slick.find(
|
||||||
|
'[data-record-id="' + img2Id + '"]'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
$notMatch.attr('data-lazy').indexOf('?unique'), -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2017 LasLabs Inc.
|
||||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<template id="assets_slickroom" inherit_id="web.assets_backend">
|
||||||
|
<xpath expr="//script[last()]" position="after">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
type="text/less"
|
||||||
|
href="/web_widget_slickroom/static/src/less/web_widget_slickroom.less"
|
||||||
|
/>
|
||||||
|
<script type="application/javascript"
|
||||||
|
src="/web_widget_slickroom/static/src/js/web_widget_slickroom.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_slickroom/static/tests/js/web_widget_slickroom.js"
|
||||||
|
/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2017 LasLabs Inc.
|
||||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||||
|
|
||||||
|
from . import test_ui
|
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2017 LasLabs Inc.
|
||||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||||
|
|
||||||
|
from odoo.tests.common import HttpCase
|
||||||
|
|
||||||
|
|
||||||
|
class UICase(HttpCase):
|
||||||
|
def test_ui_web(self):
|
||||||
|
"""Test backend tests."""
|
||||||
|
self.phantom_js(
|
||||||
|
"/web/tests?debug=assets&module=web_widget_slickroom",
|
||||||
|
"",
|
||||||
|
login="admin",
|
||||||
|
)
|
Loading…
Reference in New Issue