[IMP] web_editor, website, website_sale: add zoom option for t-field image

web_editor Qweb: Add 'zoom' option for widget image
    <span t-field="product.image_small" t-options="{'widget': 'image', 'zoom': 'image_medium'}" />

website: Add Zoom code to be standard for all website module

website_sale: add zoom on Main product image
This commit is contained in:
Jeremy Kersten
2016-09-21 21:29:18 +02:00
parent fd36e5ceda
commit f0463a3e1d
7 changed files with 353 additions and 10 deletions
+8 -2
View File
@@ -303,8 +303,14 @@ class Image(models.AbstractModel):
elif options.get('alt'):
alt = options['alt']
img = '<img class="%s" src="%s" style="%s"%s/>' % \
(classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '')
src_zoom = None
if options.get('zoom') and getattr(record, options['zoom'], None):
src_zoom = '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, options['zoom'], max_size, sha)
elif options.get('zoom'):
src_zoom = options['zoom']
img = '<img class="%s" src="%s" style="%s"%s%s/>' % \
(classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '', ' data-zoom="1" data-zoom-image="%s"' % src_zoom if src_zoom else '')
return ir_qweb.unicodifier(img)
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
+3
View File
@@ -266,6 +266,9 @@ odoo.define('website.website', function (require) {
}
});
// enable magnify on zommable img
$('.zoomable img[data-zoom]').zoomOdoo();
Dialog.include({
init: function () {
this._super.apply(this, arguments);
@@ -0,0 +1,288 @@
(function ($) {
/**
This code has been more that widely inspired by easyZoom library.
Copyright 2013 Matt Hinchliffe
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
**/
'use strict';
var dw, dh, rw, rh, lx, ly;
var defaults = {
// Attribute to retrieve the zoom image URL from.
linkTag: 'a',
linkAttribute: 'data-zoom-image',
// event to trigger zoom
event: 'click', //or mousenter
// Prevent clicks on the zoom image link.
preventClicks: true,
// Callback function to execute before the flyout is displayed.
beforeShow: $.noop,
// Callback function to execute before the flyout is removed.
beforeHide: $.noop,
// Callback function to execute when the flyout is displayed.
onShow: $.noop,
// Callback function to execute when the flyout is removed.
onHide: $.noop,
// Callback function to execute when the cursor is moved while over the image.
onMove: $.noop
};
/**
* ZoomOdoo
* @constructor
* @param {Object} target
* @param {Object} options (Optional)
*/
function ZoomOdoo(target, options) {
this.$target = $(target);
this.opts = $.extend({}, defaults, options, this.$target.data());
if (this.isOpen === undefined) {
this._init();
}
}
/**
* Init
* @private
*/
ZoomOdoo.prototype._init = function() {
this.$link = this.$target.find(this.opts.linkTag).length && this.$target.find(this.opts.linkTag) || this.$target;
this.$image = this.$target.find('img').length && this.$target.find('img') || this.$target;
this.$flyout = $('<div class="zoomodoo-flyout" />');
var $attach = this.$target;
if (this.opts.attach !== undefined && this.$target.parents(this.opts.attach).length) {
$attach = this.$target.parents(this.opts.attach);
}
$attach.parent().on('mousemove.zoomodoo touchmove.zoomodoo', $.proxy(this._onMove, this));
$attach.parent().on('mouseleave.zoomodoo touchend.zoomodoo', $.proxy(this._onLeave, this));
this.$target.parent().on(this.opts.event + '.zoomodoo touchstart.zoomodoo', $.proxy(this._onEnter, this));
if (this.opts.preventClicks) {
this.$target.on('click.zoomodoo', function(e) { e.preventDefault(); });
}
};
/**
* Show
* @param {MouseEvent|TouchEvent} e
* @param {Boolean} testMouseOver (Optional)
*/
ZoomOdoo.prototype.show = function(e, testMouseOver) {
var w1, h1, w2, h2;
var self = this;
if (this.opts.beforeShow.call(this) === false) return;
if (!this.isReady) {
return this._loadImage(this.$link.attr(this.opts.linkAttribute), function() {
if (self.isMouseOver || !testMouseOver) {
self.show(e);
}
});
}
var $attach = this.$target;
if (this.opts.attach !== undefined && this.$target.parents(this.opts.attach).length) {
$attach = this.$target.parents(this.opts.attach);
}
$attach.parent().append(this.$flyout);
w1 = this.$target.width();
h1 = this.$target.height();
w2 = this.$flyout.width();
h2 = this.$flyout.height();
dw = this.$zoom.width() - w2;
dh = this.$zoom.height() - h2;
// For the case where the zoom image is actually smaller than
// the flyout.
if (dw < 0) dw = 0;
if (dh < 0) dh = 0;
rw = dw / w1;
rh = dh / h1;
this.isOpen = true;
this.opts.onShow.call(this);
if (e) {
this._move(e);
}
};
/**
* On enter
* @private
* @param {Event} e
*/
ZoomOdoo.prototype._onEnter = function(e) {
var touches = e.originalEvent.touches;
this.isMouseOver = true;
if (!touches || touches.length === 1) {
e.preventDefault();
this.show(e, true);
}
};
/**
* On move
* @private
* @param {Event} e
*/
ZoomOdoo.prototype._onMove = function(e) {
if (!this.isOpen) return;
e.preventDefault();
this._move(e);
};
/**
* On leave
* @private
*/
ZoomOdoo.prototype._onLeave = function() {
this.isMouseOver = false;
if (this.isOpen) {
this.hide();
}
};
/**
* On load
* @private
* @param {Event} e
*/
ZoomOdoo.prototype._onLoad = function(e) {
// IE may fire a load event even on error so test the image dimensions
if (!e.currentTarget.width) return;
this.isReady = true;
this.$flyout.html(this.$zoom);
if (e.data.call) {
e.data();
}
};
/**
* Load image
* @private
* @param {String} href
* @param {Function} callback
*/
ZoomOdoo.prototype._loadImage = function(href, callback) {
var zoom = new Image();
this.$zoom = $(zoom).on('load', callback, $.proxy(this._onLoad, this));
zoom.style.position = 'absolute';
zoom.src = href;
};
/**
* Move
* @private
* @param {Event} e
*/
ZoomOdoo.prototype._move = function(e) {
if (e.type.indexOf('touch') === 0) {
var touchlist = e.touches || e.originalEvent.touches;
lx = touchlist[0].pageX;
ly = touchlist[0].pageY;
} else {
lx = e.pageX || lx;
ly = e.pageY || ly;
}
var offset = this.$target.offset();
var pt = ly - offset.top;
var pl = lx - offset.left;
var xt = Math.ceil(pt * rh);
var xl = Math.ceil(pl * rw);
// Close if outside
if (xl < 0 || xt < 0 || xl > dw || xt > dh) {
this.hide();
} else {
var top = xt * -1;
var left = xl * -1;
this.$zoom.css({
top: top,
left: left
});
this.opts.onMove.call(this, top, left);
}
};
/**
* Hide
*/
ZoomOdoo.prototype.hide = function() {
if (!this.isOpen) return;
if (this.opts.beforeHide.call(this) === false) return;
this.$flyout.detach();
this.isOpen = false;
this.opts.onHide.call(this);
};
// jQuery plugin wrapper
$.fn.zoomOdoo = function(options) {
return this.each(function() {
var api = $.data(this, 'zoomOdoo');
if (!api) {
$.data(this, 'zoomOdoo', new ZoomOdoo(this, options));
} else if (api.isOpen === undefined) {
api._init();
}
});
};
// AMD and CommonJS module compatibility
if (typeof define === 'function' && define.amd){
define(function() {
return ZoomOdoo;
});
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = ZoomOdoo;
}
})(jQuery);
@@ -0,0 +1,35 @@
.zoomodoo {
position: relative;
/* 'Shrink-wrap' the element */
display: inline-block;
*display: inline;
*zoom: 1;
img {
vertical-align: bottom;
}
}
.zoomodoo-flyout {
position:absolute;
z-index: 100;
overflow: hidden;
background: #FFF;
}
.zoomodoo-hover .zoomodoo-flyout {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.zoomodoo-next .zoomodoo-flyout {
top: 0;
left: 100%;
width: 100%;
height: 100%;
margin-left: 20px;
}
@@ -16,11 +16,13 @@
<link rel="stylesheet" type="text/less" href="/website/static/src/less/website.ui.components.less"/>
<link rel="stylesheet" type="text/less" href="/website/static/src/less/website.wrapwrap.less"/>
<link rel="stylesheet" type="text/less" href="/website/static/src/less/website.snippets.less"/>
<link rel="stylesheet" type="text/css" href="/website/static/src/less/website.zoomodoo.less"/>
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website/static/src/js/website.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.share.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.snippets.animation.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.zoomodoo.js"></script>
</xpath>
</template>
@@ -44,6 +46,7 @@
<script type="text/javascript" src="/website/static/src/js/website.snippets.editor.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.snippets.gallery.js"></script>
</template>
<!-- Layout -->
@@ -237,7 +237,7 @@ odoo.define('website_sale.website_sale', function (require) {
$img.attr("src", "/web/image/product.product/" + product_id + "/image");
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
.data('oe-model', 'product.product').data('oe-id', product_id);
$img = $(event_source).closest('tr.js_product, .oe_website_sale').find('img.js_variant_img_small');
if ($img) { // if only one, thumbnails are not displayed
$img.attr("src", "/web/image/product.product/" + product_id + "/image/90x90");
@@ -419,4 +419,6 @@ odoo.define('website_sale.website_sale', function (require) {
}
$("select[name='country_id']").change();
});
$('.ecom-zoomable img[data-zoom]').zoomOdoo({ attach: '#o-carousel-product'});
});
+13 -7
View File
@@ -82,7 +82,7 @@
</div>
<div class="oe_product_image">
<a itemprop="url" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']&gt;1 else None))">
<span itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'resize': None if product_image_big else '300x300'}" t-att-alt="product.name" />
<span itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'resize': None if product_image_big else '300x300', 'zoom': 'image'}" t-att-alt="product.name" />
</a>
</div>
<t t-if="show_publish">
@@ -397,17 +397,17 @@
</div>
<div class="row">
<div class="col-sm-7">
<span itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name'}" groups="!website_sale.group_website_multi_image"/>
<span itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image'}" groups="!website_sale.group_website_multi_image"/>
<t groups="website_sale.group_website_multi_image">
<t t-set="variant_img" t-value="product.product_variant_ids[0].image_variant"/>
<t t-set="image_ids" t-value="product.product_image_ids"/>
<div id="o-carousel-product" class="carousel slide" data-ride="carousel" data-interval="0">
<div class="carousel-outer">
<div class="carousel-inner">
<div class="item active" itemprop="image" t-field="product.product_variant_ids[0].image" t-options='{"widget": "image", "class": "product_detail_img js_variant_img", "alt-field": "name"}'/>
<div t-if="variant_img" class="item" itemprop="image" t-field="product.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name"}'/>
<div class="item active" itemprop="image" t-field="product.product_variant_ids[0].image" t-options='{"widget": "image", "class": "product_detail_img js_variant_img", "alt-field": "name", "zoom": "image"}'/>
<div t-if="variant_img" class="item" itemprop="image" t-field="product.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name", "zoom": "image"}'/>
<t t-if="len(image_ids)" t-foreach="image_ids" t-as="pimg">
<div class="item" t-field="pimg.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name"}'/>
<div class="item" t-field="pimg.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name", "zoom": "image" }'/>
</t>
</div>
@@ -483,6 +483,12 @@
</t>
</template>
<template inherit_id='website_sale.product' id="product_picture_magnify" customize_show="True" name="Activate Magnify">
<xpath expr='//div[contains(@class, "js_sale")]' position='attributes'>
<attribute name="class" separator=" " add="ecom-zoomable zoomodoo-next" />
</xpath>
</template>
<template id="recommended_products" inherit_id="website_sale.product" customize_show="True" name="Alternative Products">
<xpath expr="//div[@id='product_full_description']" position="after">
<div class="container mt32" t-if="product.alternative_product_ids">
@@ -1059,7 +1065,7 @@
<a class='a-submit btn btn-default mb16 btn-block'>
<i class="fa fa-plus-square" aria-hidden="true"></i> Add an address
</a>
</form>
</form>
</div>
<t t-foreach="shippings" t-as="ship">
<div class="col-sm-12 col-md-6 one_kanban">
@@ -1621,7 +1627,7 @@
<input type="hidden" name="tx_id" t-att-value="tx.id"/>
</form>
</template>
<template id="payment_token_form_confirm">
<t t-call="website.layout">
<t t-set="additional_title">Shop - Confirm payment</t>