From 239b6bc0b5a2a644486737f2b0b71e7e6c0a2edf Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 21 Aug 2014 18:00:23 +0200 Subject: [PATCH] [IMP] website snippet new option, gallery website.snippet: The methods described in javascript will be called automatically from the data-method_name = value on li snippets options. The methods are called in the order relative to children of xml (eg:
  • ...
  • ... , the method1 is then called method2). Methods receive two arguments: type (over, click, reset) and value. data-snippet-id-option becomes data-snippet-id. Several xml options can use the same data-snippet-id. add a new gallery snippet convert website_sale to the new option system --- .../mass_mailing/data/mass_mailing_demo.xml | 8 +- .../src/js/website_mass_mailing.editor.js | 10 +- addons/mass_mailing/views/snippets.xml | 9 +- addons/theme_bootswatch/__openerp__.py | 3 +- .../theme_bootswatch/static/src/xml/theme.xml | 107 -- addons/theme_bootswatch/views/layout.xml | 17 - addons/theme_bootswatch/views/theme.xml | 102 ++ addons/website/controllers/main.py | 57 +- addons/website/data/data.xml | 12 + addons/website/doc/website.snippet.rst | 134 +++ addons/website/static/src/css/snippets.css | 10 + addons/website/static/src/css/snippets.sass | 9 +- addons/website/static/src/css/website.css | 108 ++ addons/website/static/src/css/website.sass | 96 ++ .../website/static/src/js/website.editor.js | 4 +- .../src/js/website.snippets.animation.js | 77 +- .../static/src/js/website.snippets.editor.js | 957 ++++++++---------- .../static/src/js/website.snippets.gallery.js | 348 +++++++ addons/website/static/src/js/website.theme.js | 15 +- .../website/static/src/xml/website.editor.xml | 2 +- .../static/src/xml/website.gallery.xml | 102 ++ .../static/src/xml/website.snippets.xml | 11 +- .../website/static/src/xml/website.theme.xml | 159 --- addons/website/views/snippets.xml | 315 ++++-- addons/website/views/themes.xml | 156 +++ addons/website/views/website_templates.xml | 9 +- .../views/website_blog_templates.xml | 2 +- .../views/website_customer.xml | 2 +- addons/website_event/data/event_demo.xml | 84 +- addons/website_event/views/website_event.xml | 6 +- .../website_event_track/controllers/event.py | 2 +- .../views/website_event.xml | 8 +- .../data/hr_job_demo.xml | 56 +- .../views/templates.xml | 10 +- addons/website_mail/views/snippets.xml | 2 +- .../src/js/website_mail_group.editor.js | 10 +- addons/website_mail_group/views/snippets.xml | 6 +- .../views/website_membership.xml | 2 +- .../data/website_quotation_demo.xml | 62 +- .../website_quote/views/website_quotation.xml | 4 +- addons/website_sale/__openerp__.py | 1 + addons/website_sale/data/demo.xml | 40 +- .../static/src/css/website_sale.css | 26 +- .../static/src/css/website_sale.sass | 15 +- .../static/src/js/website_sale.editor.js | 127 ++- addons/website_sale/views/snippets.xml | 43 + addons/website_sale/views/templates.xml | 59 -- .../website_twitter/views/twitter_snippet.xml | 2 +- openerp/addons/base/module/module_data.xml | 5 + .../addons/base/tests/test_mail_examples.py | 36 +- openerp/tools/mail.py | 2 +- 51 files changed, 2170 insertions(+), 1279 deletions(-) delete mode 100644 addons/theme_bootswatch/static/src/xml/theme.xml delete mode 100644 addons/theme_bootswatch/views/layout.xml create mode 100644 addons/website/doc/website.snippet.rst create mode 100644 addons/website/static/src/js/website.snippets.gallery.js create mode 100644 addons/website/static/src/xml/website.gallery.xml delete mode 100644 addons/website/static/src/xml/website.theme.xml create mode 100644 addons/website_sale/views/snippets.xml diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml index 2bbfcce4512..6157be3fbe1 100644 --- a/addons/mass_mailing/data/mass_mailing_demo.xml +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -39,7 +39,7 @@ - + @@ -64,7 +64,7 @@
    -
    +
    @@ -108,7 +108,7 @@ [('customer', '=', True)]email]]> - +
    @@ -133,7 +133,7 @@
    -
    +
    diff --git a/addons/mass_mailing/static/src/js/website_mass_mailing.editor.js b/addons/mass_mailing/static/src/js/website_mass_mailing.editor.js index 4caba1d29b5..0a460caaa81 100644 --- a/addons/mass_mailing/static/src/js/website_mass_mailing.editor.js +++ b/addons/mass_mailing/static/src/js/website_mass_mailing.editor.js @@ -5,8 +5,9 @@ var _t = openerp._t; website.snippet.options.mailing_list_subscribe = website.snippet.Option.extend({ - on_prompt: function () { + choose_mailing_list: function (type, value) { var self = this; + if (type !== "click") return; return website.prompt({ id: "editor_new_mailing_list_subscribe_button", window_title: _t("Add a Newsletter Subscribe Button"), @@ -22,15 +23,10 @@ drop_and_build_snippet: function() { var self = this; this._super(); - this.on_prompt().fail(function () { + this.choose_mailing_list('click').fail(function () { self.editor.on_remove(); }); }, - start : function () { - var self = this; - this.$el.find(".js_mailing_list").on("click", _.bind(this.on_prompt, this)); - this._super(); - }, clean_for_save: function () { this.$target.addClass("hidden"); }, diff --git a/addons/mass_mailing/views/snippets.xml b/addons/mass_mailing/views/snippets.xml index e05e680206f..8641ebf6be0 100644 --- a/addons/mass_mailing/views/snippets.xml +++ b/addons/mass_mailing/views/snippets.xml @@ -29,13 +29,10 @@ -
    -
  • - Change Newsletter -
  • + data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel"> +
  • Change Newsletter
  • diff --git a/addons/theme_bootswatch/__openerp__.py b/addons/theme_bootswatch/__openerp__.py index 6af402c307a..5d051136dd3 100644 --- a/addons/theme_bootswatch/__openerp__.py +++ b/addons/theme_bootswatch/__openerp__.py @@ -1,12 +1,11 @@ { 'name': 'Bootswatch Theme', 'description': 'Bootswatch Theme', - 'category': 'Website', + 'category': 'Theme', 'version': '1.0', 'author': 'OpenERP SA', 'depends': ['website'], 'data': [ - 'views/layout.xml', 'views/theme.xml', ], 'application': True, diff --git a/addons/theme_bootswatch/static/src/xml/theme.xml b/addons/theme_bootswatch/static/src/xml/theme.xml deleted file mode 100644 index 0eff6acc4df..00000000000 --- a/addons/theme_bootswatch/static/src/xml/theme.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -
    - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    - - - - - -
    - - - - - -
    - - - - - -
    -
    -
    -
    - - - - - - - diff --git a/addons/theme_bootswatch/views/layout.xml b/addons/theme_bootswatch/views/layout.xml deleted file mode 100644 index 2f5cd624c16..00000000000 --- a/addons/theme_bootswatch/views/layout.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - diff --git a/addons/theme_bootswatch/views/theme.xml b/addons/theme_bootswatch/views/theme.xml index 2cb5fda3612..9b9964cc551 100644 --- a/addons/theme_bootswatch/views/theme.xml +++ b/addons/theme_bootswatch/views/theme.xml @@ -2,6 +2,108 @@ + + diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 5b29ff743fe..d2666ab8ab8 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -273,46 +273,51 @@ class Website(openerp.addons.web.controllers.main.Home): @http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True) def attach(self, func, upload=None, url=None): - Attachments = request.registry['ir.attachment'] + # the upload argument doesn't allow us to access the files if more than + # one file is uploaded, as upload references the first file + # therefore we have to recover the files from the request object + Attachments = request.registry['ir.attachment'] # registry for the attachment table - website_url = message = None - if not upload: - website_url = url - name = url.split("/").pop() + uploads = [] + message = None + if not upload: # no image provided, storing the link and the image name + uploads.append({'website_url': url}) + name = url.split("/").pop() # recover filename attachment_id = Attachments.create(request.cr, request.uid, { 'name':name, 'type': 'url', 'url': url, 'res_model': 'ir.ui.view', }, request.context) - else: + else: # images provided try: - image_data = upload.read() - image = Image.open(cStringIO.StringIO(image_data)) - w, h = image.size - if w*h > 42e6: # Nokia Lumia 1020 photo resolution - raise ValueError( - u"Image size excessive, uploaded images must be smaller " - u"than 42 million pixel") - - attachment_id = Attachments.create(request.cr, request.uid, { - 'name': upload.filename, - 'datas': image_data.encode('base64'), - 'datas_fname': upload.filename, - 'res_model': 'ir.ui.view', - }, request.context) - - [attachment] = Attachments.read( - request.cr, request.uid, [attachment_id], ['website_url'], - context=request.context) - website_url = attachment['website_url'] + for c_file in request.httprequest.files.getlist('upload'): + image_data = c_file.read() + image = Image.open(cStringIO.StringIO(image_data)) + w, h = image.size + if w*h > 42e6: # Nokia Lumia 1020 photo resolution + raise ValueError( + u"Image size excessive, uploaded images must be smaller " + u"than 42 million pixel") + + attachment_id = Attachments.create(request.cr, request.uid, { + 'name': c_file.filename, + 'datas': image_data.encode('base64'), + 'datas_fname': c_file.filename, + 'res_model': 'ir.ui.view', + }, request.context) + + [attachment] = Attachments.read( + request.cr, request.uid, [attachment_id], ['website_url'], + context=request.context) + uploads.append(attachment) except Exception, e: logger.exception("Failed to upload image to attachment") message = unicode(e) return """""" % (func, json.dumps(website_url), json.dumps(message)) + """ % (func, json.dumps(uploads), json.dumps(message)) @http.route(['/website/publish'], type='json', auth="public", website=True) def publish(self, id, object): diff --git a/addons/website/data/data.xml b/addons/website/data/data.xml index c5254d65c0f..08ef965104c 100644 --- a/addons/website/data/data.xml +++ b/addons/website/data/data.xml @@ -52,6 +52,18 @@
    + + Website Theme + ir.module.module + form + kanban,form + + +

    No theme module found!

    +

    You should try others search criteria.

    +
    +
    + Website / diff --git a/addons/website/doc/website.snippet.rst b/addons/website/doc/website.snippet.rst new file mode 100644 index 00000000000..9eec8431fcc --- /dev/null +++ b/addons/website/doc/website.snippet.rst @@ -0,0 +1,134 @@ +Website Snippet & Blocks +======================== + +The building blocks appear in the edit bar website. These prebuilt html block +allowing the designer to easily generate content on a page (drag and drop). +Snippets bind javascript object on custom part html code according to their +selector (jQuery) and javascript object. The snippets is also used to create +the drop zone. + + +Building Blocks ++++++++++++++++ + +Overwrite ``_get_snippet_url`` to set an other file to load the snippets (use by +website_mail for example) +Overwrite ``_add_check_selector`` to enable or disable other snippets. By default +the builder check if the node or his parent have the attribute data-oe-model + +Trigger: +- ``snippet-dropped`` is triggered on ``#oe_snippets`` whith $target as attribute when a snippet is dropped +- ``snippet-activated`` is triggered on ``#oe_snippets`` (and on snippet) when a snippet is activated + + +Blocks +++++++ + +The ``blocks`` are the HTML code that can be drop in the page. The blocks consist +of a body and a thumbnail: + - thumbnail: + (have class ``oe_snippet_thumbnail``) contains a picture and a text used to + display a preview in the edit bar that contains all the block list + - body: + (have class ``oe_snippet_body``) is the real part dropped in the page. The class + ``oe_snippet_body`` is removed before inserting the block in the page. +e.g.: +
    +
    + + ...Block Name... +
    +
    + +
    +
    + + +Editor +++++++ + +The ``editor`` is the frame placed above the block being edited who contains buttons +(move, delete, clone) and customize menu. The ``editor`` load ``options`` based on +selectors defined in snippets + + +Options ++++++++ + +The ``option`` is the javascript object used to customize the HTML code. + +Object: + - this.``$target``: + block html inserted inside the page + - this.``$el``: + html li list of this options + - this.``$overlay``: + html editor overlay who content resize bar, customize menu... + +Methods: + - ``set_active``: + highlight the customize menu item when the user click on customize, and click on + an item. + - ``start``: + called when the editor is created on the DOM + - ``on_focus``: + called when the user click inside the block inserted in page and when the + user drop on block into the page + - ``on_blur``: + called when the user click outside the block inserted in page, if the block + is focused + - ``on_clone``: + called when the snippet is duplicate + @variables: $clone is allready inserted is the page + - ``on_remove``: + called when the snippet is removed (dom is removing after this tigger) + - ``drop_and_build_snippet: + called just after that a thumbnail is drag and dropped into a drop zone. + The content is already inserted in the page. + - ``clean_for_save``: + is called just before to save the vue. Sometime it's important to remove or add + some datas (contentEditable, added classes to a running animation...) + +Customize Methods: +All javascript option can defiend method call from the template on mouse over, on +click or to reset the default value (
  • ...
  • ). +The method receive the variable type (``over``, ``click`` or ``reset``), the method +value and the jQuery object of the HTML li item. (can be use for multi methods) + +By default to custom method are defined: + + - ``check_class(type, className, $li)``: + li must have data-check_class="a_classname_for_test" to call this method. This method + toggle the className on $target + - ``select_class(type, className, $li)``: + This method remove all other select_class value (for this option) and add this current ClassName + + +Snippet ++++++++ + +The ``snippets`` are the HTML code to defined the drop zone and the linked javascript object. +All HTML li tag defined inside the snippets HTML are insert into the customize menu. All +data attributes is optional: + +- ``data-selector``: + Apply options on all The part of html who match with this jQuery selector. + E.g.: If the selector is div, all div will be selected and can be highlighted and assigned an editor. +- ``data-option``: + javascript ``option`` to call when the ``editor`` is loaded +- ``data-drop-in``: + The html part can be insert or move beside the selected html block (jQuery selector) +- ``data-drop-near``: + The html part can be insert or move inside the selected html block (jQuery selector) +- ``data-drop-in-vertical``: + The html part can be insert or move inside the selected html block. The drop zone is displayed + vertically (jQuery selector) +- HTML content like
  • ...
  • : + List of HTML li menu items displayed in customize menu. If the li tag have datas the methods are + automatically called +- ``no-check``: + The selectors are automatically compute to have elements inside the branding. If you use this option + the check is not apply (for e.g.: to have a snippet for the grid view of website_sale) \ No newline at end of file diff --git a/addons/website/static/src/css/snippets.css b/addons/website/static/src/css/snippets.css index a1c42b0b814..ebe87769794 100644 --- a/addons/website/static/src/css/snippets.css +++ b/addons/website/static/src/css/snippets.css @@ -46,6 +46,7 @@ display: inline-block; white-space: nowrap; background: black; + margin-right: 60px; } #oe_snippets .tab-content > div { background: black; @@ -119,6 +120,12 @@ .oe_snippet > :not(.oe_snippet_thumbnail) { display: none !important; } +.oe_snippet.disable .oe_snippet_thumbnail { + background-color: rgba(255, 0, 0, 0.75); +} +.oe_snippet.disable .oe_snippet_thumbnail_img { + opacity: 0.8; +} #oe_snippets .oe_snippet_thumbnail { background: #747474, -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, rgba(0, 0, 0, 0.25)), color-stop(100%, rgba(0, 0, 0, 0.4))); @@ -381,6 +388,9 @@ float: none; margin: 0 -3px; } +.oe_overlay .oe_overlay_options > .btn-group > div { + float: none; +} .oe_overlay .oe_overlay_options .btn, .oe_overlay .oe_overlay_options a { cursor: pointer; } diff --git a/addons/website/static/src/css/snippets.sass b/addons/website/static/src/css/snippets.sass index 71613f8dfbe..a94b8665e0c 100644 --- a/addons/website/static/src/css/snippets.sass +++ b/addons/website/static/src/css/snippets.sass @@ -43,6 +43,7 @@ display: inline-block white-space: nowrap background: black + margin-right: 60px > div background: rgb(0,0,0) label @@ -87,7 +88,11 @@ line-height: 18px & > :not(.oe_snippet_thumbnail) display: none !important - + &.disable + .oe_snippet_thumbnail + background-color: rgba(255, 0, 0, 0.75) + .oe_snippet_thumbnail_img + opacity: 0.8 #oe_snippets .oe_snippet_thumbnail @include background(#747474, radial-gradient(rgba(0,0,0,0.25),rgba(0,0,0,0.4))) @@ -286,6 +291,8 @@ display: inline-block float: none margin: 0 -3px + > div + float: none .btn, a cursor: pointer .dropdown diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index c5d403bfdda..055ac7fb4e3 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -356,6 +356,114 @@ ul.nav-stacked > li > a { padding: 4px 0; } +/* gallery */ +.o_gallery.o_grid.o_spc-none div.row, .o_gallery.o_masonry.o_spc-none div.row { + margin: 0; +} +.o_gallery.o_grid.o_spc-none div.row > *, .o_gallery.o_masonry.o_spc-none div.row > * { + padding: 0; +} +.o_gallery.o_grid.o_spc-small div.row, .o_gallery.o_masonry.o_spc-small div.row { + margin: 5px 0; +} +.o_gallery.o_grid.o_spc-small div.row > *, .o_gallery.o_masonry.o_spc-small div.row > * { + padding: 0 5px; +} +.o_gallery.o_grid.o_spc-medium div.row, .o_gallery.o_masonry.o_spc-medium div.row { + margin: 10px 0; +} +.o_gallery.o_grid.o_spc-medium div.row > *, .o_gallery.o_masonry.o_spc-medium div.row > * { + padding: 0 10px; +} +.o_gallery.o_grid.o_spc-big div.row, .o_gallery.o_masonry.o_spc-big div.row { + margin: 15px 0; +} +.o_gallery.o_grid.o_spc-big div.row > *, .o_gallery.o_masonry.o_spc-big div.row > * { + padding: 0 15px; +} +.o_gallery.o_grid .img, .o_gallery.o_masonry .img { + width: 100%; +} +.o_gallery.o_grid.size-auto .row { + height: auto; +} +.o_gallery.o_grid.size-small .row { + height: 100px; +} +.o_gallery.o_grid.size-medium .row { + height: 250px; +} +.o_gallery.o_grid.size-big .row { + height: 400px; +} +.o_gallery.o_grid.size-small .img, .o_gallery.o_grid.size-medium .img, .o_gallery.o_grid.size-big .img { + height: 100%; +} +.o_gallery.o_nomode.o_spc-none .img { + padding: 0; +} +.o_gallery.o_nomode.o_spc-small .img { + padding: 5px; +} +.o_gallery.o_nomode.o_spc-medium .img { + padding: 10px; +} +.o_gallery.o_nomode.o_spc-big .img { + padding: 15px; +} +.o_gallery.o_slideshow .carousel ul.carousel-indicators li { + border: 1px solid #aaaaaa; +} +.o_gallery .carousel-inner .item img { + max-width: none; +} + +.o_gallery.o_slideshow > .container { + height: 100%; +} + +.o_gallery.o_slideshow .carousel, .modal-body.o_slideshow .carousel { + height: 100%; +} +.o_gallery.o_slideshow .carousel img, .modal-body.o_slideshow .carousel img { + max-height: 100%; + max-width: 100%; + margin: auto; + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} +.o_gallery.o_slideshow .carousel ul.carousel-indicators, .modal-body.o_slideshow .carousel ul.carousel-indicators { + display: block; + height: auto; + padding: 0; + border-width: 0; + position: absolute; + bottom: 0; +} +.o_gallery.o_slideshow .carousel ul.carousel-indicators li, .modal-body.o_slideshow .carousel ul.carousel-indicators li { + list-style-image: none; + display: inline-block; + width: 35px; + height: 35px; + margin: 0 0px 5px 5px; + padding: 0; + border: 1px solid #aaaaaa; + text-indent: initial; + background-size: cover; + opacity: 0.5; + background-color: black; +} +.o_gallery.o_slideshow .carousel ul.carousel-indicators li.active, .modal-body.o_slideshow .carousel ul.carousel-indicators li.active { + opacity: 1; +} +.o_gallery.o_slideshow .carousel .carousel-control.left, .o_gallery.o_slideshow .carousel .carousel-control.right, .modal-body.o_slideshow .carousel .carousel-control.left, .modal-body.o_slideshow .carousel .carousel-control.right { + background-image: none; + background-color: transparent; +} + /* Parallax Theme */ div.carousel .carousel-indicators li { border: 1px solid grey; diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index f4cf73437f8..ba47a12c90f 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -285,6 +285,102 @@ ul.nav-stacked > li > a .hr padding: 4px 0 +/* gallery */ + +.o_gallery + &.o_grid, &.o_masonry + &.o_spc-none + div.row + margin: 0 + div.row > * + padding: 0 + &.o_spc-small + div.row + margin: 5px 0 + div.row > * + padding: 0 5px + &.o_spc-medium + div.row + margin: 10px 0 + div.row > * + padding: 0 10px + &.o_spc-big + div.row + margin: 15px 0 + div.row > * + padding: 0 15px + .img + width: 100% + &.o_grid + &.size-auto .row + height: auto + &.size-small .row + height: 100px + &.size-medium .row + height: 250px + &.size-big .row + height: 400px + &.size-small, &.size-medium, &.size-big + .img + height: 100% + &.o_nomode + &.o_spc-none + .img + padding: 0 + &.o_spc-small + .img + padding: 5px + &.o_spc-medium + .img + padding: 10px + &.o_spc-big + .img + padding: 15px + + &.o_slideshow .carousel ul.carousel-indicators li + border: 1px solid #aaa + .carousel-inner .item img + max-width: none + +.o_gallery.o_slideshow > .container + height: 100% + +.o_gallery.o_slideshow .carousel, .modal-body.o_slideshow .carousel + height: 100% + img + max-height: 100% + max-width: 100% + margin: auto + position: relative + top: 50% + -webkit-transform: translateY(-50%) + -ms-transform: translateY(-50%) + transform: translateY(-50%) + ul.carousel-indicators + display: block + height: auto + padding: 0 + border-width: 0 + position: absolute + bottom: 0 + li + list-style-image: none + display: inline-block + width: 35px + height: 35px + margin: 0 0px 5px 5px + padding: 0 + border: 1px solid #aaa + text-indent: initial + background-size: cover + opacity: 0.5 + background-color: #000 + li.active + opacity: 1 + .carousel-control.left, .carousel-control.right + background-image: none + background-color: transparent + /* Parallax Theme */ div.carousel diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 9b86002626c..205d8dde076 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -1568,9 +1568,9 @@ } var callback = _.uniqueId('func_'); this.$('input[name=func]').val(callback); - window[callback] = function (url, error) { + window[callback] = function (attachments, error) { delete window[callback]; - self.file_selected(url, error); + self.file_selected(attachments[0]['website_url'], error); }; }, file_selection: function () { diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index c72e2c3b84e..785027795ce 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -1,7 +1,9 @@ (function () { 'use strict'; + var website = openerp.website; + if (!website.snippet) website.snippet = {}; website.snippet.readyAnimation = []; @@ -37,7 +39,14 @@ } }); }; + + $(document).ready(function () { + if ($(".o_gallery:not(.oe_slideshow)").size()) { + // load gallery modal template + website.add_template_file('/website/static/src/xml/website.gallery.xml'); + } + website.snippet.start_animation(); }); @@ -69,7 +78,7 @@ website.snippet.animationRegistry.slider = website.snippet.Animation.extend({ selector: ".carousel", start: function () { - this.$target.carousel({interval: 10000}); + this.$target.carousel(); }, stop: function () { this.$target.carousel('pause'); @@ -154,4 +163,70 @@ this.$target.html('
     
    '); }, }); + + /* ------------------------------------------------------------------------- + Gallery Animation + + This ads a Modal window containing a slider when an image is clicked + inside a gallery + -------------------------------------------------------------------------*/ + website.snippet.animationRegistry.gallery = website.snippet.Animation.extend({ + selector: ".o_gallery:not(.o_slideshow)", + start: function() { + var self = this; + this.$el.on("click", "img", this.click_handler); + }, + click_handler : function(event) { + var self = this; + var $cur = $(event.currentTarget); + var edition_mode = ($cur.closest("[contenteditable='true']").size() !== 0); + + // show it only if not in edition mode + if (!edition_mode) { + var urls = [], + idx = undefined, + milliseconds = undefined, + params = undefined, + $images = $cur.closest(".o_gallery").find("img"), + dimensions = { + min_width : Math.round( window.innerWidth * 0.7), + min_height : Math.round( window.innerHeight * 0.7), + max_width : Math.round( window.innerWidth * 0.7), + max_height : Math.round( window.innerHeight * 0.7), + height : Math.round( window.innerHeight * 0.7) + }; + + $images.each(function() { + urls.push($(this).attr("src")); + }); + var $img = ($cur.is("img") === true) ? $cur : $cur.closest("img"); + idx = urls.indexOf($img.attr("src")); + + milliseconds = $cur.closest(".o_gallery").data("interval") || false; + var params = { + srcs : urls, + index: idx, + dim : dimensions, + interval : milliseconds, + id: _.uniqueId("slideshow_") + }; + var $modal = $(openerp.qweb.render('website.gallery.slideshow.lightbox', params)); + $modal.modal({ + keyboard : true, + backdrop : true + }); + $modal.on('hidden.bs.modal', function() { + $(this).hide(); + $(this).siblings().filter(".modal-backdrop").remove(); // bootstrap leaves a modal-backdrop + $(this).remove(); + + }); + $modal.find(".modal-content, .modal-body.o_slideshow").css("height", "100%"); + + $modal.appendTo(document.body); + $modal.find(".carousel").carousel(); + } + } // click_handler + }); + })(); diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 4630206748f..c4ca26f2f74 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -1,135 +1,6 @@ (function () { 'use strict'; -/* Building block / Snippet Editor - - The building blocks appear in the edit bar website. These prebuilt html block - allowing the designer to easily generate content on a page (drag and drop). - Options allow snippets to add customizations part html code according to their - selector (jQuery) and javascript object. - - How to create content? - - Designers can add their own html block in the "snippets" (/website/views/snippets.xml). - The block must be added in one of four menus (structure, content, feature or effect). - Structure: -
    -
    - - ...Block Name... -
    -
    - ... - -
    -
    - - How to create options? - - Designers can add their own html block in the "snippet_options" (/website/views/snippets.xml). - Structure: - -
    - data-selector="..." - data-selector-siblings="..." - data-selector-children="..." - data-selector-vertical-children='...'> - ... -
  • ...
  • - ... - -
    - - How to create a javascript object for an options? - - openerp.website.snippet.options["...option-id..."] = website.snippet.Option.extend({ - // start is called when the user click into a block or when the user drop a block - // into the page (just after the init method). - // start is usually used to bind event. - // - // this.$target: block html inserted inside the page - // this.$el: html li list of this options - // this.$overlay: html editor overlay who content resize bar, customize menu... - start: function () {}, - - - // onFocus is called when the user click inside the block inserted in page - // and when the user drop on block into the page - onFocus : function () {}, - - - // onBlur is called when the user click outside the block inserted in page, if - // the block is focused - onBlur : function () {}, - - - // on_clone is called when the snippet is duplicate - // @variables: $clone is allready inserted is the page - on_clone: function ($clone) {}, - - - // on_remove is called when the snippet is removed (dom is removing after this tigger) - on_remove: function () {}, - - - // drop_and_build_snippet is called just after that a thumbnail is drag and dropped - // into a drop zone. The content is already inserted in the page. - drop_and_build_snippet: function () {}, - - // select is called when a user select an item in the li list of options - // By default, if the li item have a data-value attribute, the data-vlue it's apply - // like a class to the html block (this.$target) - // @variables: next_previous = {$next, $prev} - // $next = next item selected or false - // $prev = previous item selected or false - select: function (event, next_previous) {} - - // preview is called when a user is on mouse over or mouse out of an item - // variables: next_previous = {$next, $prev} - // $next = next item selected or false - // $prev = previous item selected or false - preview: function (event, next_previous) {} - - // clean_for_save - // clean_for_save is called just before to save the vue - // Sometime it's important to remove or add some datas (contentEditable, added - // classes to a running animation...) - clean_for_save: function () {} - }); - - - // 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped - // 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated - -*/ - var dummy = function () {}; var website = openerp.website; @@ -177,8 +48,25 @@ } }); + $.extend($.expr[':'],{ + checkData: function(node,i,m){ + var dataName = m[3]; + while (node) { + if (node.dataset && node.dataset[dataName]) { + return true; + } else { + node = node.parentNode; + } + } + return false; + }, + hasData: function(node,i,m){ + return !!_.toArray(node.dataset).length; + }, + }); + if (!website.snippet) website.snippet = {}; - website.snippet.templateOptions = {}; + website.snippet.templateOptions = []; website.snippet.globalSelector = ""; website.snippet.selector = []; website.snippet.BuildingBlock = openerp.Widget.extend({ @@ -198,25 +86,6 @@ subtree: true, }); }, - dom_filter: function (dom, sibling) { - if (typeof dom === "string") { - var include = "[data-oe-model]"; - var sdom = dom.split(','); - dom = ""; - _.each(sdom, function (val) { - val = val.replace(/^\s+|\s+$/g, ''); - dom += include + " " + val + ", "; - if (!sibling) { - val = val.split(" "); - dom += val.shift() + include + val.join(" ") + ", "; - } - }); - dom = dom.replace(/,\s*$/g, ''); - return $(dom); - } else { - return (!sibling && $(dom).is("[data-oe-model]")) || $(dom).parents("[data-oe-model]").length ? $(dom) : $(""); - } - }, start: function() { var self = this; @@ -224,18 +93,16 @@ .prependTo(this.parent.$("#website-top-edit ul")) .find("button"); - this.$button.click(function () { - self.make_active(false); - self.$el.toggleClass("hidden"); - }); - $("#wrapwrap").click(function () { + this.$button.click(_.bind(this.show_blocks, this)); + + this.$snippet = $("#oe_snippets"); + this.$wrapwrap = $("#wrapwrap"); + this.$wrapwrap.click(function () { self.$el.addClass("hidden"); }); this.fetch_snippet_templates(); - this.bind_snippet_click_editor(); - this.$el.addClass("hidden"); $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) { @@ -245,11 +112,69 @@ this.getParent().on('change:height', this, function (editor) { self.$el.css('top', editor.get('height')); }); - self.$el.css('top', this.parent.get('height')); + this.$el.css('top', this.parent.get('height')); + }, + show_blocks: function () { + var self = this; + this.make_active(false); + this.$el.toggleClass("hidden"); + if (this.$el.hasClass("hidden")) { + return; + } + + //this.enable_snippets( this.$snippet.find(".tab-pane.active") ); + var categories = this.$snippet.find(".tab-pane.active") + .add(this.$snippet.find(".tab-pane:not(.active)")) + .get().reverse(); + function enable() { + self.enable_snippets( $(categories.pop()) ); + if (categories.length) { + setTimeout(enable,10); + } + } + setTimeout(enable,0); + }, + enable_snippets: function ($category) { + var self = this; + $category.find(".oe_snippet_body").each(function () { + var $snippet = $(this); + + if (!$snippet.data('selectors')) { + var selectors = []; + for (var k in website.snippet.templateOptions) { + var option = website.snippet.templateOptions[k]; + if ($snippet.is(option.base_selector)) { + + var dropzone = []; + if (option['drop-near']) dropzone.push(option['drop-near']); + if (option['drop-in']) dropzone.push(option['drop-in']); + if (option['drop-in-vertical']) dropzone.push(option['drop-in-vertical']); + selectors = selectors.concat(dropzone); + } + } + $snippet.data('selectors', selectors.length ? selectors.join(":first, ") + ":first" : ""); + } + + if ($snippet.data('selectors').length && self.$wrapwrap.find($snippet.data('selectors')).size()) { + $snippet.closest(".oe_snippet").removeClass("disable"); + } else { + $snippet.closest(".oe_snippet").addClass("disable"); + } + }); + $('#oe_snippets .scroll a[data-toggle="tab"][href="#' + $category.attr("id") + '"]') + .toggle(!!$category.find(".oe_snippet:not(.disable)").size()); }, _get_snippet_url: function () { return '/website/snippets'; }, + _add_check_selector : function (selector, no_check) { + var data = selector.split(","); + var selectors = []; + for (var k in data) { + selectors.push(data[k].replace(/^\s+|\s+$/g, '') + (no_check ? "" : ":checkData(oeModel)")); + } + return selectors.join(", "); + }, fetch_snippet_templates: function () { var self = this; @@ -258,35 +183,31 @@ var $html = $(html); var selector = []; - var $styles = $html.find("[data-snippet-option-id]"); + var $styles = $html.find("[data-option], [data-selector]"); $styles.each(function () { var $style = $(this); - var style_id = $style.data('snippet-option-id'); - website.snippet.templateOptions[style_id] = { - 'snippet-option-id' : style_id, - 'selector': $style.data('selector'), + var no_check = $style.data('no-check'); + var option_id = $style.data('option'); + var option = { + 'option' : option_id, + 'base_selector': $style.data('selector'), + 'selector': self._add_check_selector($style.data('selector'), no_check), '$el': $style, - 'selector-siblings': $style.data('selector-siblings'), - 'selector-children': $style.data('selector-children'), - 'selector-vertical-children': $style.data('selector-vertical-children'), + 'drop-near': $style.data('drop-near') && self._add_check_selector($style.data('drop-near'), no_check), + 'drop-in': $style.data('drop-in') && self._add_check_selector($style.data('drop-in'), no_check), + 'drop-in-vertical': $style.data('drop-in-vertical') && self._add_check_selector($style.data('drop-in-vertical'), no_check), 'data': $style.data() }; - selector.push($style.data('selector')); + website.snippet.templateOptions.push(option); + selector.push(option.selector); }); $styles.addClass("hidden"); website.snippet.globalSelector = selector.join(","); self.$snippets = $html.find(".tab-content > div > div").addClass("oe_snippet"); + self.$el.append($html); - - var snippets = 0; - self.$snippets.each(function () { - if (self.snippet_have_dropzone($(this))) - snippets++; - }); - if (!snippets) self.$button.css("display", "none"); - self.make_snippet_draggable(self.$snippets); }); }, @@ -310,17 +231,10 @@ hide: function () { this.$el.addClass("hidden"); }, - - snippet_have_dropzone: function ($snippet) { - return (($snippet.data('selector-siblings') && this.dom_filter($snippet.data('selector-siblings')).size() > 0) || - ($snippet.data('selector-children') && this.dom_filter($snippet.data('selector-children')).size() > 0) || - ($snippet.data('selector-vertical-children') && this.dom_filter($snippet.data('selector-vertical-children')).size() > 0)); - }, - bind_snippet_click_editor: function () { var self = this; var snipped_event_flag; - $("#wrapwrap").on('click', function (event) { + self.$wrapwrap.on('click', function (event) { if (snipped_event_flag || !event.srcElement) { return; } @@ -337,9 +251,6 @@ $target = $target.parents(website.snippet.globalSelector).first(); } - if (!self.dom_filter($target).length) { - $target = false; - } if (self.$active_snipped_id && self.$active_snipped_id.is($target)) { return; } @@ -349,14 +260,14 @@ snippet_blur: function ($snippet) { if ($snippet) { if ($snippet.data("snippet-editor")) { - $snippet.data("snippet-editor").onBlur(); + $snippet.data("snippet-editor").on_blur(); } } }, snippet_focus: function ($snippet) { if ($snippet) { if ($snippet.data("snippet-editor")) { - $snippet.data("snippet-editor").onFocus(); + $snippet.data("snippet-editor").on_focus(); } } }, @@ -364,15 +275,15 @@ var self = this; var options = website.snippet.options; var template = website.snippet.templateOptions; - for (var k in options) { - if (template[k] && options[k].prototype.clean_for_save !== dummy) { - var $snippet = this.dom_filter(template[k].selector); - $snippet.each(function () { - new options[k](self, null, $(this), k).clean_for_save(); + for (var k in template) { + var Option = options[template[k]['option']]; + if (Option && Option.prototype.clean_for_save !== dummy) { + self.$wrapwrap.find(template[k].selector).each(function () { + new Option(self, null, $(this), k).clean_for_save(); }); } } - $("*[contentEditable], *[attributeEditable]") + self.$wrapwrap.find("*[contentEditable], *[attributeEditable]") .removeAttr('contentEditable') .removeAttr('attributeEditable'); }, @@ -392,7 +303,7 @@ this.create_overlay(this.$active_snipped_id); this.snippet_focus($snippet); } - $("#oe_snippets").trigger('snippet-activated', $snippet); + this.$snippet.trigger('snippet-activated', $snippet); if ($snippet) { $snippet.trigger('snippet-activated', $snippet); } @@ -406,15 +317,6 @@ this.cover_target($snippet.data('overlay'), $snippet); }, - path_eval: function(path){ - var obj = window; - path = path.split('.'); - do{ - obj = obj[path.shift()]; - }while(path.length && obj); - return obj; - }, - // activate drag and drop for the snippets in the snippet toolbar make_snippet_draggable: function($snippets){ var self = this; @@ -437,32 +339,32 @@ start: function(){ self.hide(); dropped = false; - // snippet_selectors => to get selector-siblings, selector-children, selector-vertical-children + // snippet_selectors => to get drop-near, drop-in, drop-in-vertical $snippet = $(this); - $toInsert = $snippet.find('.oe_snippet_body').clone(); - + var $base_body = $snippet.find('.oe_snippet_body'); var selector = []; var selector_siblings = []; var selector_children = []; var selector_vertical_children = []; for (var k in website.snippet.templateOptions) { - if ($toInsert.is(website.snippet.templateOptions[k].selector)) { - selector.push(website.snippet.templateOptions[k].selector); - if (website.snippet.templateOptions[k]['selector-siblings']) - selector_siblings.push(website.snippet.templateOptions[k]['selector-siblings']); - if (website.snippet.templateOptions[k]['selector-children']) - selector_children.push(website.snippet.templateOptions[k]['selector-children']); - if (website.snippet.templateOptions[k]['selector-vertical-children']) - selector_vertical_children.push(website.snippet.templateOptions[k]['selector-vertical-children']); + if ($base_body.is(website.snippet.templateOptions[k].base_selector)) { + selector.push(website.snippet.templateOptions[k].base_selector); + if (website.snippet.templateOptions[k]['drop-near']) + selector_siblings.push(website.snippet.templateOptions[k]['drop-near']); + if (website.snippet.templateOptions[k]['drop-in']) + selector_children.push(website.snippet.templateOptions[k]['drop-in']); + if (website.snippet.templateOptions[k]['drop-in-vertical']) + selector_vertical_children.push(website.snippet.templateOptions[k]['drop-in-vertical']); } } + $toInsert = $base_body.clone(); action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate'; if( action === 'insert'){ if (!selector_siblings.length && !selector_children.length && !selector_vertical_children.length) { - console.debug($snippet.data("snippet-id") + " have oe_snippet_body class and have not for insert action"+ - "data-selector-siblings, data-selector-children or data-selector-vertical-children tag for mutate action"); + console.debug($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: "+ + "data-drop-near, data-drop-in or data-drop-in-vertical"); return; } self.activate_insertion_zones({ @@ -473,7 +375,7 @@ } else if( action === 'mutate' ){ if (!$snippet.data('selector')) { - console.debug($snippet.data("snippet-id") + " have not oe_snippet_body class and have not data-selector tag"); + console.debug($snippet.data("option") + " have not oe_snippet_body class and have not data-selector tag"); return; } var $targets = self.activate_overlay_zones(selector_children.join(",")); @@ -504,38 +406,24 @@ stop: function(ev, ui){ $toInsert.removeClass('oe_snippet_body'); - if (action === 'insert' && ! dropped && $('.oe_drop_zone') && ui.position.top > 3) { - var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first(); + if (action === 'insert' && ! dropped && self.$wrapwrap.find('.oe_drop_zone') && ui.position.top > 3) { + var el = self.$wrapwrap.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first(); if (el.length) { el.after($toInsert); dropped = true; } } - $('.oe_drop_zone').droppable('destroy').remove(); + self.$wrapwrap.find('.oe_drop_zone').droppable('destroy').remove(); if (dropped) { var $target = false; $target = $toInsert; setTimeout(function () { - $("#oe_snippets").trigger('snippet-dropped', $target); + self.$snippet.trigger('snippet-dropped', $target); website.snippet.start_animation(true, $target); - // drop_and_build_snippet - self.create_overlay($target); - if ($target.data("snippet-editor")) { - $target.data("snippet-editor").drop_and_build_snippet(); - } - for (var k in website.snippet.templateOptions) { - $target.find(website.snippet.templateOptions[k].selector).each(function () { - var $snippet = $(this); - self.create_overlay($snippet); - if ($snippet.data("snippet-editor")) { - $snippet.data("snippet-editor").drop_and_build_snippet(); - } - }); - } // reset snippet for rte $target.removeData("snippet-editor"); @@ -551,7 +439,22 @@ $snippet.removeData("overlay"); } }); + // end + + // drop_and_build_snippet self.create_overlay($target); + if ($target.data("snippet-editor")) { + $target.data("snippet-editor").drop_and_build_snippet(); + } + for (var k in website.snippet.templateOptions) { + $target.find(website.snippet.templateOptions[k].selector).each(function () { + var $snippet = $(this); + self.create_overlay($snippet); + if ($snippet.data("snippet-editor")) { + $snippet.data("snippet-editor").drop_and_build_snippet(); + } + }); + } // end self.make_active($target); @@ -566,7 +469,7 @@ // return the original snippet in the editor bar from a snippet id (string) get_snippet_from_id: function(id){ return $('.oe_snippet').filter(function(){ - return $(this).data('snippet-id') === id; + return $(this).data('option') === id; }).first(); }, @@ -584,7 +487,7 @@ var zone_template = "
    "; if(child_selector){ - self.dom_filter(child_selector).each(function (){ + self.$wrapwrap.find(child_selector).each(function (){ var $zone = $(this); $zone.find('> *:not(.oe_drop_zone):visible').after(zone_template); $zone.prepend(zone_template); @@ -592,7 +495,7 @@ } if(vertical_child_selector){ - self.dom_filter(vertical_child_selector).each(function (){ + self.$wrapwrap.find(vertical_child_selector).each(function (){ var $zone = $(this); var $template = $(zone_template).addClass("oe_vertical"); var nb = 0; @@ -623,7 +526,7 @@ } if(sibling_selector){ - self.dom_filter(sibling_selector, true).each(function (){ + self.$wrapwrap.find(sibling_selector, true).each(function (){ var $zone = $(this); if($zone.prev('.oe_drop_zone:visible').length === 0){ $zone.before(zone_template); @@ -641,13 +544,13 @@ // count += $zones.length; // $zones.remove(); - $zones = $('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove(); // no recursive zones + $zones = self.$wrapwrap.find('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove(); // no recursive zones count += $zones.length; $zones.remove(); } while (count > 0); // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones. - var $zones = $('.oe_drop_zone:not(.oe_vertical)'); + var $zones = self.$wrapwrap.find('.oe_drop_zone:not(.oe_vertical)'); $zones.each(function (){ var zone = $(this); var prev = zone.prev(); @@ -671,14 +574,9 @@ // generate drop zones covering the elements selected by the selector // we generate overlay drop zones only to get an idea of where the snippet are, the drop activate_overlay_zones: function(selector){ - var $targets = this.dom_filter(selector); + var $targets = typeof selector === "string" ? this.$wrapwrap.find(selector) : selector; var self = this; - if (typeof selector !== 'string' && !$targets.length) { - console.debug( "A good node must have a [data-oe-model] attribute or must have at least one parent with [data-oe-model] attribute."); - console.debug( "Wrong node(s): ", selector); - } - function is_visible($el){ return $el.css('display') != 'none' && $el.css('opacity') != '0' @@ -719,9 +617,14 @@ $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () { self.cover_target($zone, $target); }); - $('body').on("resize", function () { - self.cover_target($zone, $target); - }); + var resize = function () { + if ($zone.parent().length) { + self.cover_target($zone, $target); + } else { + $('body').off("resize", resize); + } + }; + $('body').on("resize", resize); } self.cover_target($target.data('overlay'), $target); }); @@ -733,97 +636,86 @@ website.snippet.options = {}; website.snippet.Option = openerp.Class.extend({ // initialisation (don't overwrite) - init: function (BuildingBlock, editor, $target, snippet_id) { + init: function (BuildingBlock, editor, $target, option_id) { this.BuildingBlock = BuildingBlock; this.editor = editor; this.$target = $target; + var option = website.snippet.templateOptions[option_id]; var styles = this.$target.data("snippet-option-ids") || {}; - styles[snippet_id] = this; + styles[option_id] = this; this.$target.data("snippet-option-ids", styles); this.$overlay = this.$target.data('overlay'); - this['snippet-option-id'] = snippet_id; - var $option = website.snippet.templateOptions[snippet_id].$el; - this.$el = $option.find(">li").clone(); - this.data = $option.data(); - - this.required = this.$el.data("required"); + this.option= option_id; + this.$el = option.$el.find(">li").clone(); + this.data = option.$el.data(); this.set_active(); - this.$el.find('li[data-value] a').on('mouseenter mouseleave click', _.bind(this._mouse, this)); - this.$el.not(':not([data-value])').find("a").on('mouseenter mouseleave click', _.bind(this._mouse, this)); - this.$target.on('snippet-style-reset', _.bind(this.set_active, this)); + this.$target.on('snippet-option-reset', _.bind(this.set_active, this)); + this._bind_li_menu(); this.start(); }, + + _bind_li_menu: function () { + this.$el.filter("li:hasData").find('a:first') + .off('mouseenter click') + .on('mouseenter click', _.bind(this._mouse, this)); + + this.$el + .off('mouseenter click', "li:hasData a") + .on('mouseenter click', "li:hasData a", _.bind(this._mouse, this)); + + this.$el.closest("ul").add(this.$el) + .off('mouseleave') + .on('mouseleave', _.bind(this.reset, this)); + + this.$el + .off('mouseleave', "ul") + .on('mouseleave', "ul", _.bind(this.reset, this)); + + this.reset_methods = []; + this.reset_time = null; + }, + + /** + * this method handles mouse:over and mouse:leave on the snippet editor menu + */ + _time_mouseleave: null, _mouse: function (event) { - var self = this; - - if (event.type === 'mouseleave') { - if (!this.over) return; - this.over = false; - } else if (event.type === 'click') { - this.over = false; - }else { - this.over = true; - } - - var $prev, $next; - if (event.type === 'mouseleave') { - $prev = $(event.currentTarget).parent(); - $next = this.$el.find("li[data-value].active"); - } else { - $prev = this.$el.find("li[data-value].active"); - $next = $(event.currentTarget).parent(); - } - if (!$prev.length) { - $prev = false; - } - if ($prev && $prev[0] === $next[0]) { - $next = false; - if (this.required) { - return; - } - } - - var np = {'$next': $next, '$prev': $prev}; + var $next = $(event.currentTarget).parent(); + // triggers preview or apply methods if a menu item has been clicked + this.select(event.type === "click" ? "click" : "over", $next); if (event.type === 'click') { - setTimeout(function () { - self.set_active(); - self.$target.trigger("snippet-style-change", [self, np]); - },0); - this.select({'$next': $next, '$prev': $prev}); + this.set_active(); + this.$target.trigger("snippet-option-change", [this]); } else { - setTimeout(function () { - self.$target.trigger("snippet-style-preview", [self, np]); - },0); - this.preview(np); + this.$target.trigger("snippet-option-preview", [this]); } }, - /* set_active + /* * select and set item active or not (add highlight item and his parents) * called before start */ set_active: function () { - var self = this; - this.$el.find('li').removeClass("active"); - var $active = this.$el.find('li[data-value]') - .filter(function () { - var $li = $(this); - return ($li.data('value') && self.$target.hasClass($li.data('value'))); - }) - .first() + var classes = _.uniq((this.$target.attr("class") || '').split(/\s+/)); + this.$el.find('[data-check_class], [data-select_class]') + .add(this.$el) + .filter('[data-check_class], [data-select_class]') + .removeClass("active") + .filter('[data-check_class="' + classes.join('"], [data-check_class="') + '"] ,'+ + '[data-select_class="' + classes.join('"], [data-select_class="') + '"]') .addClass("active"); - this.$el.find('li:has(li[data-value].active)').addClass("active"); }, start: function () { }, - onFocus : function () { + on_focus : function () { + this._bind_li_menu(); }, - onBlur : function () { + on_blur : function () { }, on_clone: function ($clone) { @@ -835,110 +727,167 @@ drop_and_build_snippet: function () { }, - select: function (np) { - var self = this; - // add or remove html class - if (np.$prev && this.required) { - this.$target.removeClass(np.$prev.data('value' || "")); - } - if (np.$next) { - this.$target.addClass(np.$next.data('value') || ""); + reset: function (event) { + var self = this, $el, method; + for (var k in self.reset_methods) { + method = self.reset_methods[k]; + var $li = self.$el.find('[data-'+method+']').add(self.$el).filter('.active, :has(.active)'); + if ($li.size()) { + $li.each(function () { + self.select("reset", $(this)); + }); + } else { + self[method]("reset", null, null); + } } + self.reset_methods = []; + self.$target.trigger("snippet-option-reset", [this]); }, - preview: function (np) { - var self = this; + // call data-method args as method + select: function (type, $li) { + var self = this, + $methods = [], + el = $li[0], + $el; + clearTimeout(this.reset_time); - // add or remove html class - if (np.$prev) { - this.$target.removeClass(np.$prev.data('value') || ""); + function filter (k) { return k !== 'oeId' && k !== 'oeModel' && k !== 'oeField' && k !== 'oeXpath' && k !== 'oeSourceId';} + function hasData(el) { + for (var k in el.dataset) { + if (filter (k)) { + return true; + } + } + return false; } - if (np.$next) { - this.$target.addClass(np.$next.data('value') || ""); + function method(el) { + var data = {}; + for (var k in el.dataset) { + if (filter (k)) { + data[k] = el.dataset[k]; + } + } + return data; } + + while (el && this.$el.is(el) || _.some(this.$el.map(function () {return $.contains(this, el);}).get()) ) { + if (hasData(el)) { + $methods.push(el); + } + el = el.parentNode; + } + + $methods.reverse(); + + _.each($methods, function (el) { + var $el = $(el); + var methods = method(el); + for (var k in methods) { + if (self[k]) { + if (self.reset_methods.indexOf(k) === -1) { + self.reset_methods.push(k); + } + self[k](type, methods[k], $el); + } else { + console.error("'"+self.option+"' snippet have not method '"+k+"'"); + } + } + }); + }, + + // default method for snippet + check_class: function (type, value, $li) { + var $lis = this.$el.find('[data-check_class]').add(this.$el).filter('[data-check_class]'); + + function map ($lis) { + return $lis.map(function () {return $(this).data("check_class");}).get().join(" "); + } + var classes = map($lis); + var active_classes = map($lis.filter('.active, :has(.active)')); + + this.$target.removeClass(classes); + this.$target.addClass(active_classes); + + if (type !== 'reset') { + this.$target.toggleClass(value); + } + }, + select_class: function (type, value, $li) { + var $lis = this.$el.find('[data-select_class]').add(this.$el).filter('[data-select_class]'); + + var classes = $lis.map(function () {return $(this).data('select_class');}).get(); + + this.$target.removeClass(classes.join(" ")); + if(value) this.$target.addClass(value); }, clean_for_save: dummy }); - website.snippet.options.background = website.snippet.Option.extend({ - _get_bg: function () { - return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); - }, - _set_bg: function (src) { - this.$target.css("background-image", src && src !== "" ? 'url(' + src + ')' : ""); - }, start: function () { this._super(); - var src = this._get_bg(); - this.$el.find("li[data-value].active.oe_custom_bg").data("src", src); + var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); + if (this.$target.hasClass('oe_custom_bg')) { + this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src); + } }, - select: function(np) { - var self = this; - this._super(np); - if (np.$next) { - if (np.$next.hasClass("oe_custom_bg")) { - var $image = $(''); - $image.attr("src", np.$prev ? np.$prev.data("src") : ''); - $image.appendTo(self.$target); - - self.element = new CKEDITOR.dom.element($image[0]); - var editor = new website.editor.MediaDialog(self, self.element); - editor.appendTo(document.body); - editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden'); - - $image.on('saved', self, function (o) { - var src = $image.attr("src"); - self._set_bg(src); - np.$next.data("src", src); - self.$target.trigger("snippet-style-change", [self, np]); - $image.remove(); - }); - editor.on('cancel', self, function () { - if (!np.$prev || np.$prev.data("src") === "") { - self.$target.removeClass(np.$next.data("value")); - self.$target.trigger("snippet-style-change", [self, np]); - } - $image.remove(); - }); - } else { - this._set_bg(np.$next.data("src")); - } + background: function(type, value, $li) { + if (value && value.length) { + this.$target.css("background-image", 'url(' + value + ')'); } else { - this._set_bg(false); - this.$target.removeClass(np.$prev.data("value")); + this.$target.css("background-image", "").removeClass("oe_img_bg"); } }, - preview: function (np) { - this._super(np); - if (np.$next) { - this._set_bg(np.$next.data("src")); - } + choose_image: function(type, value, $li) { + if(type !== "click") return; + + var self = this; + var $image = $(''); + $image.attr("src", value); + $image.appendTo(self.$target); + + self.element = new CKEDITOR.dom.element($image[0]); + var editor = new website.editor.MediaDialog(self, self.element); + editor.appendTo(document.body); + editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden'); + + $image.on('saved', self, function (o) { + var value = $image.attr("src"); + self.$target.css("background-image", 'url(' + value + ')'); + self.$el.find('li[data-select_class="oe_custom_bg"]').data("src", value); + self.$target.trigger("snippet-option-change", [self]); + $image.remove(); + self.$target.addClass('oe_custom_bg').removeClass('oe_img_bg'); + self.set_active(); + }); + editor.on('cancel', self, function () { + self.$target.removeClass("oe_img_bg") + .trigger("snippet-option-change", [self]); + $image.remove(); + }); }, set_active: function () { var self = this; - var bg = self.$target.css("background-image"); - this.$el.find('li').removeClass("active"); - this.$el.find('li').removeClass("btn-primary"); - var $active = this.$el.find('li[data-value]') - .filter(function () { - var $li = $(this); - return ($li.data('src') && bg.indexOf($li.data('src')) >= 0) || - (!$li.data('src') && self.$target.hasClass($li.data('value'))); - }) - .first(); - if (!$active.length) { - $active = this.$target.css("background-image") !== 'none' ? - this.$el.find('li[data-value].oe_custom_bg') : - this.$el.find('li[data-value=""]'); - } + var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); + this._super(); - //don't set active on an OpenDialog link, else it not possible to click on it again after. - // TODO in Saas-4 - Once bootstrap is in less - // - add a class active-style to get the same display but without the active behaviour used by bootstrap in JS. - var classStr = _.string.contains($active[0].className, "oe_custom_bg") ? "btn-primary" : "active"; - $active.addClass(classStr); - this.$el.find('li:has(li[data-value].active)').addClass(classStr); + this.$el.find('li[data-background]') + .filter(':not([data-background=""])') + .removeClass("active") + .each(function () { + var background = $(this).data("background"); + if ((src.length && background.length && src.indexOf(background) !== -1) || (!src.length && !background.length)) { + $(this).addClass("active"); + } + }); + if(!this.$el.find('li.active').size()) { + this.$el.find('li[data-select_class=""][data-background=""], '+ + 'li[data-select_class=""] li[data-background=""], '+ + 'li[data-background=""] li[data-select_class=""]').addClass("active"); + this.$el.filter('li[data-select_class=""]').find('li[data-background=""]').addClass("active"); + this.$el.filter('li[data-background=""]').find('li[data-select_class=""]').addClass("active"); + } } }); @@ -983,7 +932,6 @@ } }); - website.snippet.options.slider = website.snippet.Option.extend({ unique_id: function () { var id = 0; @@ -1029,14 +977,12 @@ this.id = this.$target.attr("id"); this.$inner = this.$target.find('.carousel-inner'); this.$indicators = this.$target.find('.carousel-indicators'); - - this.$el.find(".js_add").on('click', function () {self.on_add_slide(); return false;}); - this.$el.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;}); - this.$target.carousel('pause'); this.rebind_event(); }, - on_add_slide: function () { + add_slide: function (type, value) { + if(type !== "click") return; + var self = this; var cycle = this.$inner.find('.item').length; var $active = this.$inner.find('.item.active, .item.prev, .item.next').first(); @@ -1054,7 +1000,9 @@ },0); return $clone; }, - on_remove_slide: function () { + remove_slide: function (type, value) { + if(type !== "click") return; + if (this.remove_process) { return; } @@ -1083,6 +1031,13 @@ this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); } }, + interval : function(type, value) { + this.$target.attr("data-interval", value); + }, + set_active: function () { + this.$el.find('li[data-interval]').removeClass("active") + .filter('li[data-interval='+this.$target.attr("data-interval")+']').addClass("active"); + }, }); website.snippet.options.carousel = website.snippet.options.slider.extend({ getSize: function () { @@ -1092,12 +1047,11 @@ }, clean_for_save: function () { this._super(); - this.$target.css("background-image", ""); - this.$target.removeClass(this._class); + this.$target.removeClass('oe_img_bg ' + this._class).css("background-image", ""); }, load_style_options : function () { this._super(); - $(".snippet-style-size li[data-value='']").remove(); + $(".snippet-option-size li[data-value='']").remove(); }, start : function () { var self = this; @@ -1108,46 +1062,30 @@ if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c; return self._class || ""; }; - this.$target.on('snippet-style-change snippet-style-preview', function (event, style, np) { - var $active = self.$target.find(".item.active"); - if (style['snippet-option-id'] === "size") return; - if (style['snippet-option-id'] === "background") { - $active.css("background-image", self.$target.css("background-image")); + this.$target.on('slid.bs.carousel', function () { + if(self.editor && self.editor.styles.background) { + self.editor.styles.background.$target = self.$target.find(".item.active"); + self.editor.styles.background.set_active(); } - if (np.$prev) { - $active.removeClass(np.$prev.data("value")); - } - if (np.$next) { - $active.addClass(np.$next.data("value")); - add_class(np.$next.data("value")); - } - }); - this.$target.on('slid', function () { // slide.bs.carousel - var $active = self.$target.find(".item.active"); - self.$target - .css("background-image", $active.css("background-image")) - .removeClass(add_class($active.attr("class"))) - .addClass($active.attr("class")) - .trigger("snippet-style-reset"); - self.$target.carousel("pause"); }); - this.$target.trigger('slid'); + this.$target.trigger('slid.bs.carousel'); }, - on_add_slide: function () { - var $clone = this._super(); + add_slide: function (type, data) { + if(type !== "click") return; + var $clone = this._super(type, data); // choose an other background var bg = this.$target.data("snippet-option-ids").background; if (!bg) return $clone; - var $styles = bg.$el.find("li[data-value]:not(.oe_custom_bg)"); - var styles_index = $styles.index($styles.filter(".active")[0]); - $styles.removeClass("active"); - var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]); + var $styles = bg.$el.find("li[data-background]"); + var $select = $styles.filter(".active").removeClass("active").next("li[data-background]"); + if (!$select.length) { + $select = $styles.first(); + } $select.addClass("active"); - $clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : ""); - $clone.addClass($select.data("value") || ""); + $clone.css("background-image", $select.data("background") ? "url('"+ $select.data("background") +"')" : ""); return $clone; }, @@ -1162,7 +1100,6 @@ this._super(); }, }); - website.snippet.options.marginAndResize = website.snippet.Option.extend({ start: function () { var self = this; @@ -1290,7 +1227,7 @@ return this.grid; }, - onFocus : function () { + on_focus : function () { this._super(); this.change_cursor(); }, @@ -1390,7 +1327,7 @@ hide_remove_button: function() { this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length); }, - onFocus : function () { + on_focus : function () { this._super(); this.hide_remove_button(); }, @@ -1401,20 +1338,8 @@ return false; }, on_remove: function () { - if (!this.$target.siblings().length) { - var $parent = this.$target.parents(".row:first"); - if($parent.find("[class*='col-md']").length > 1) { - return false; - } else { - if (!$parent.data("snippet-editor")) { - this.BuildingBlock.create_overlay($parent); - } - $parent.data("snippet-editor").on_remove(); - } - } this._super(); this.hide_remove_button(); - return false; }, on_resize: function (compass, beginClass, current) { if (compass === 'w') { @@ -1441,7 +1366,7 @@ }, }); - website.snippet.options["resize"] = website.snippet.options.marginAndResize.extend({ + website.snippet.options.resize = website.snippet.options.marginAndResize.extend({ getSize: function () { this.grid = this._super(); this.grid.size = 8; @@ -1465,7 +1390,7 @@ this.$target.data("snippet-view", new website.snippet.animationRegistry.parallax(this.$target)); } this.scroll(); - this.$target.on('snippet-style-change snippet-style-preview', function () { + this.$target.on('snippet-option-change snippet-option-preview', function () { self.$target.data("snippet-view").set_values(); }); this.$target.attr('contentEditable', 'false'); @@ -1474,22 +1399,15 @@ this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true'); }, - scroll: function () { - var self = this; - var $ul = this.$el.find('ul[name="parallax-scroll"]'); - var $li = $ul.find("li"); - var speed = this.$target.data('scroll-background-ratio') || 0.6 ; - $ul.find('[data-value="' + speed + '"]').addClass('active'); - $li.on('click', function (event) { - $li.removeClass("active"); - $(this).addClass("active"); - var speed = $(this).data('value'); - self.$target.attr('data-scroll-background-ratio', speed); - self.$target.data("snippet-view").set_values(); - return false; - }); + scroll: function (type, value) { + this.$target.attr('data-scroll-background-ratio', value); this.$target.data("snippet-view").set_values(); }, + set_active: function () { + var value = this.$target.attr('data-scroll-background-ratio') || 0; + this.$el.find('[data-scroll]').removeClass("active") + .filter('[data-scroll="' + (this.$target.attr('data-scroll-background-ratio') || 0) + '"]').addClass("active"); + }, clean_for_save: function () { this._super(); this.$target.find(".parallax") @@ -1502,24 +1420,22 @@ start: function () { var self = this; this._super(); - - this.$el.find(".clear-style").click(function (event) { - self.$target.removeClass("fa-spin").attr("style", ""); - self.resetTransfo(); - }); - - this.$el.find(".style").click(function (event) { - var settings = self.$target.data("transfo").settings; - self.$target.transfo({ hide: (settings.hide = !settings.hide) }); - }); - this.$overlay.find('.oe_snippet_clone, .oe_handles').addClass('hidden'); - this.$overlay.find('[data-toggle="dropdown"]') .on("mousedown", function () { self.$target.transfo("hide"); }); }, + style: function (type, value) { + if (type !== 'click') return; + var settings = this.$target.data("transfo").settings; + this.$target.transfo({ hide: (settings.hide = !settings.hide) }); + }, + clear_style: function (type, value) { + if (type !== 'click') return; + this.$target.removeClass("fa-spin").attr("style", ""); + this.resetTransfo(); + }, resetTransfo: function () { var self = this; this.$target.transfo("destroy"); @@ -1541,10 +1457,10 @@ }) .mouseover(); }, - onFocus : function () { + on_focus : function () { this.resetTransfo(); }, - onBlur : function () { + on_blur : function () { this.$target.transfo("hide"); }, }); @@ -1557,7 +1473,7 @@ website.snippet.start_animation(true, this.$target); $(document.body).on("media-saved", self, function (event, prev , item) { - self.editor.onBlur(); + self.editor.on_blur(); self.BuildingBlock.make_active(false); if (self.$target.parent().data("oe-field") !== "image") { self.BuildingBlock.make_active($(item)); @@ -1571,7 +1487,7 @@ new website.editor.MediaDialog(self, self.element).appendTo(document.body); }); }, - onFocus : function () { + on_focus : function () { var self = this; if (this.$target.parent().data("oe-field") === "image") { this.$overlay.addClass("hidden"); @@ -1582,39 +1498,16 @@ }, }); - website.snippet.Editor = openerp.Class.extend({ init: function (BuildingBlock, dom) { this.BuildingBlock = BuildingBlock; this.$target = $(dom); this.$overlay = this.$target.data('overlay'); - this.snippet_id = this.$target.data("snippet-id"); - this._readXMLData(); this.load_style_options(); this.get_parent_block(); this.start(); }, - /* - * _readXMLData - * Read data XML and set value into: - * this.$el : - * all xml data - * this.$overlay : - * Dom hover the $target who content options - */ - _readXMLData: function() { - var self = this; - if(this && this.BuildingBlock && this.BuildingBlock.$snippets) { - this.$el = this.BuildingBlock.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone(); - } - var $options = this.$overlay.find(".oe_overlay_options"); - if ($options.find(".oe_options ul li").length) { - $options.find(".oe_options").removeClass("hidden"); - } - }, - - // activate drag and drop for the snippets in the snippet toolbar _drag_and_drop: function(){ var self = this; @@ -1700,18 +1593,18 @@ this.selector_siblings = []; this.selector_children = []; this.selector_vertical_children = []; - _.each(website.snippet.templateOptions, function (val) { + _.each(website.snippet.templateOptions, function (val, option_id) { if (!self.$target.is(val.selector)) { return; } - if (val['selector-siblings']) self.selector_siblings.push(val['selector-siblings']); - if (val['selector-children']) self.selector_children.push(val['selector-children']); - if (val['selector-vertical-children']) self.selector_vertical_children.push(val['selector-vertical-children']); + if (val['drop-near']) self.selector_siblings.push(val['drop-near']); + if (val['drop-in']) self.selector_children.push(val['drop-in']); + if (val['drop-in-vertical']) self.selector_vertical_children.push(val['drop-in-vertical']); - var style = val['snippet-option-id']; - var Editor = website.snippet.options[style] || website.snippet.Option; - var editor = self.styles[style] = new Editor(self.BuildingBlock, self, self.$target, style); - $ul.append(editor.$el.addClass("snippet-style-" + style)); + var option = val['option']; + var Editor = website.snippet.options[option] || website.snippet.Option; + var editor = self.styles[option] = new Editor(self.BuildingBlock, self, self.$target, option_id); + $ul.append(editor.$el.addClass("snippet-option-" + option)); }); this.selector_siblings = this.selector_siblings.join(","); if (this.selector_siblings === "") @@ -1724,7 +1617,7 @@ this.selector_vertical_children = false; if (!this.selector_siblings && !this.selector_children && !this.selector_vertical_children) { - this.$overlay.find(".oe_snippet_move").addClass('hidden'); + this.$overlay.find(".oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove").addClass('hidden'); } @@ -1772,13 +1665,35 @@ }, on_remove: function () { - this.onBlur(); + this.on_blur(); var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0)); for (var i in this.styles){ this.styles[i].on_remove(); } delete this.BuildingBlock.snippets[index]; + + // remove node and his empty + var parent, + node = this.$target.parent()[0]; + this.$target.remove(); + function check(node) { + if ($(node).outerHeight() > 8) { + return false; + } + for (var k=0; k'), + $imgs = this.get_imgs().wrap('
    ').parent(); + + this.replace($row); + + $imgs.each(function () { + var img = this.childNodes[0]; + if (img.width >= img.height * 2) { + $(this).addClass("col-md-6"); + } else if (img.width > 600) { + $(this).addClass("col-md-6"); + } else { + $(this).addClass("col-md-3"); + } + }); + $row.append($imgs); + this.$target.css("height", ""); + }, + masonry : function() { + var self = this, + $imgs = this.get_imgs(), + columns = this.get_columns(), + colClass = undefined, + $cols = []; + + var $row = $("
    "); + this.replace($row); + + // if no columns let's default to 3, here we must update the DOM accordingly :'( + if (columns === 0) { + columns = 3; + this.$target.attr("data-columns", columns); + } + colClass = "col-md-"+(12/columns); + + // create columns + for (var c = 0; c < columns; c++) { + var $col = $('
    ').addClass(colClass); + $row.append($col); + $cols.push($col.get()[0]); + } + + this.block = true; + $cols = $($cols); + var imgs = $imgs.get(); + function add() { + self.lowest($cols).append(imgs.pop()); + if (imgs.length) setTimeout(add, 0); + else self.block = false; + } + if (imgs.length) add(); + this.$target.css("height", ""); + }, + grid : function() { + var self = this, + $cols = this.get_imgs().wrap('
    ').parent(), + $col, $img, + $row = $('
    '), + columns = this.get_columns() || 3, + colClass = "col-md-"+(12/columns), + $container = this.replace($row); + + $cols.each(function(index) { // 0 based index + $col = $(this); + $img = $col.find('img'); + self.img_preserve_styles($img); + self.img_responsive($img); + $col.addClass(colClass); + $col.appendTo($row); + if ( (index+1) % columns === 0) { + $row = $('
    '); + $row.appendTo($container); + } + }); + this.$target.css("height", ""); + }, + slideshow :function () { + var self = this; + var $imgs = this.$target.find("img").detach(), + urls = $imgs.map(function() { return $(this).attr("src"); } ).get(), + params = { + srcs : urls, + index: 1, + title: "", + interval : this.$target.data("interval") || false, + id: _.uniqueId("slideshow_") + }, + $slideshow = $(openerp.qweb.render('website.gallery.slideshow', params)); + this.replace($slideshow); + this.$target.css("height", Math.round(window.innerHeight*0.7)); + }, + columns : function(type, value) { + if (this.block) return; + this.$target.attr("data-columns", value); + this.reapply(); + }, + albumimages : function(type, value) { + if(type === "click") this[value](); + }, + images_add : function() { + /* will be moved in ImageDialog from MediaManager */ + var self = this, + $container = this.$target.find(".container:first"), + $upload_form = $(openerp.qweb.render('website.gallery.dialog.upload')), + $progress = $upload_form.find(".fa"); + + $upload_form.appendTo(document.body); + + $upload_form.on('modal.bs.hide', function() { $(this).remove(); } ); + + $upload_form.find(".alert-success").hide(); + $upload_form.find(".alert-danger").hide(); + + $upload_form.on("change", 'input[name="upload"]', function(event) { + $upload_form.find('input[type="submit"]').parent().removeClass("hidden"); + $upload_form.find(".alert").hide(); + }); + + $upload_form.find("form").on("submit", function(event) { + event.preventDefault(); + var files = $(this).find('input[type="file"]')[0].files; + var formData = new FormData(); + for (var i = 0; i < files.length; i++ ) { + var file = files[i]; + formData.append('upload', file, file.name); + } + + /* + * hide submit button + */ + $('input[name="upload"], input[type="submit"]', this).parent().addClass("hidden"); + + /* + * show progress + */ + $progress.removeClass("hidden"); + + /* + * Images upload callback + */ + var callback = _.uniqueId('func_'); + formData.append("func", callback); + window[callback] = function (attachments, error) { + delete window[callback]; + + $('input[name="upload"]', this).parent().removeClass("hidden"); + + if (error) { /* failure */ + + $upload_form.find(".alert-danger").show(); + + } else { /* success */ + + $upload_form.find(".alert-success").show(); + for (var i = 0 ; i < attachments.length; i++) { + $('') + .attr("src", attachments[i].website_url) + .appendTo($container); + } + $progress.addClass("hidden"); + $upload_form.remove(); + self.reapply(); // refresh the $target + } + }; + + /* + * Images upload : don't change order of contentType & processData + * and don't change their values, otherwise the XHR will be + * wrongly conceived by jQuery. + * + * (missing boundary in the content-type header field) + * Leading to an upload failure. + */ + $.ajax('/website/attach', { + type: 'POST', + data: formData, + contentType: false, /* multipart/form-data for files */ + processData: false, + dataType: 'text' + }).done(function (script) { + $(script).appendTo('head').remove(); + }); + }); + $upload_form.modal({ backdrop : false }); + + $upload_form.find('input[name="upload"]').click(); + }, + images_rm : function() { + this.replace($('