Browse Source
[ADD] web_widget_slickroom: Create module
[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
Brenton Hughes
7 years ago
committed by
Jairo Llopis
10 changed files with 416 additions and 0 deletions
-
94web_widget_slickroom/README.rst
-
3web_widget_slickroom/__init__.py
-
22web_widget_slickroom/__manifest__.py
-
BINweb_widget_slickroom/static/description/icon.png
-
67web_widget_slickroom/static/src/js/web_widget_slickroom.js
-
6web_widget_slickroom/static/src/less/web_widget_slickroom.less
-
179web_widget_slickroom/static/tests/js/web_widget_slickroom.js
-
25web_widget_slickroom/templates/assets.xml
-
5web_widget_slickroom/tests/__init__.py
-
15web_widget_slickroom/tests/test_ui.py
@ -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", |
||||
|
], |
||||
|
} |
After Width: 128 | Height: 128 | 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", |
||||
|
) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue