diff --git a/assets/js/src/form_editor/form_editor.js b/assets/js/src/form_editor/form_editor.js new file mode 100644 index 0000000000..9733b70cbe --- /dev/null +++ b/assets/js/src/form_editor/form_editor.js @@ -0,0 +1,1013 @@ +/* + * name: MailPoet Form Editor + * author: Jonathan Labreuille + * company: Wysija + * framework: prototype 1.7.2 + */ +'use strict'; + +Event.cacheDelegated = {}; +Object.extend(document, (function() { + var cache = Event.cacheDelegated; + + function getCacheForSelector(selector) { + return cache[selector] = cache[selector] || {}; + } + + function getWrappersForSelector(selector, eventName) { + var c = getCacheForSelector(selector); + return c[eventName] = c[eventName] || []; + } + + function findWrapper(selector, eventName, handler) { + var c = getWrappersForSelector(selector, eventName); + return c.find(function(wrapper) { + return wrapper.handler === handler + }); + } + + function destroyWrapper(selector, eventName, handler) { + var c = getCacheForSelector(selector); + if(!c[eventName]) return false; + var wrapper = findWrapper(selector, eventName, handler) + c[eventName] = c[eventName].without(wrapper); + return wrapper; + } + + function createWrapper(selector, eventName, handler, context) { + var wrapper, c = getWrappersForSelector(selector, eventName); + if(c.pluck('handler').include(handler)) return false; + wrapper = function(event) { + var element = event.findElement(selector); + if(element) handler.call(context || element, event, element); + }; + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + return { + delegate: function(selector, eventName, handler, context) { + var wrapper = createWrapper.apply(null, arguments); + if(wrapper) document.observe(eventName, wrapper); + return document; + }, + stopDelegating: function(selector, eventName, handler) { + var length = arguments.length; + switch(length) { + case 2: + getWrappersForSelector(selector, eventName).each(function(wrapper) { + document.stopDelegating(selector, eventName, wrapper.handler); + }); + break; + case 1: + Object.keys(getCacheForSelector(selector)).each(function(eventName) { + document.stopDelegating(selector, eventName); + }); + break; + case 0: + Object.keys(cache).each(function(selector) { + document.stopDelegating(selector); + }); + break; + default: + var wrapper = destroyWrapper.apply(null, arguments); + if(wrapper) document.stopObserving(eventName, wrapper); + } + return document; + } + } +})()); + +var Observable = (function() { + function getEventName(name, namespace) { + name = name.substring(2); + if(namespace) name = namespace + ':' + name; + return name.underscore().split('_').join(':'); + } + + function getHandlers(klass) { + var proto = klass.prototype, + namespace = proto.namespace; + return Object.keys(proto).grep(/^on/).inject($H(), function(handlers, name) { + if(name === 'onDomLoaded') return handlers; + handlers.set(getEventName(name, namespace), getWrapper(proto[name], klass)); + return handlers; + }); + } + + function getWrapper(handler, klass) { + return function(event) { + return handler.call(new klass(this), event, event.memo); + } + } + + function onDomLoad(selector, klass) { + $$(selector).each(function(element) { + new klass(element).onDomLoaded(); + }); + } + return { + observe: function(selector) { + if(!this.handlers) this.handlers = {}; + if(this.handlers[selector]) return; + var klass = this; + if(this.prototype.onDomLoaded) document.loaded ? onDomLoad(selector, klass) : document.observe('dom:loaded', onDomLoad.curry(selector, klass)); + this.handlers[selector] = getHandlers(klass).each(function(handler) { + document.delegate(selector, handler.key, handler.value); + }); + }, + stopObserving: function(selector) { + if(!this.handlers || !this.handlers[selector]) return; + this.handlers[selector].each(function(handler) { + document.stopDelegating(selector, handler.key, handler.value); + }); + delete this.handlers[selector]; + } + } +})(); + +// override droppables +Object.extend(Droppables, { + deactivate: Droppables.deactivate.wrap(function(proceed, drop, draggable) { + if(drop.onLeave) drop.onLeave(draggable, drop.element); + return proceed(drop); + }), + activate: Droppables.activate.wrap(function(proceed, drop, draggable) { + if(drop.onEnter) drop.onEnter(draggable, drop.element); + return proceed(drop); + }), + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + this.drops.each(function(drop) { + if(Droppables.isAffected(point, element, drop)) affected.push(drop); + }); + if(affected.length > 0) drop = Droppables.findDeepestChild(affected); + if(this.last_active && this.last_active !== drop) this.deactivate(this.last_active, element); + if(drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + if(drop !== this.last_active) Droppables.activate(drop, element); + } + }, + displayArea: function(draggable) { + if(!this.drops.length) return; + + // hide controls when displaying drop areas. + WysijaForm.hideBlockControls(); + + this.drops.each(function(drop, iterator) { + if(drop.element.hasClassName('block_placeholder')) { + drop.element.addClassName('active'); + } + }); + }, + hideArea: function() { + if(!this.drops.length) return; + this.drops.each(function(drop, iterator) { + if(drop.element.hasClassName('block_placeholder')) { + drop.element.removeClassName('active'); + } else if(drop.element.hasClassName('image_placeholder')) { + drop.element.removeClassName('active'); + drop.element.up().removeClassName('active'); + } else if(drop.element.hasClassName('text_placeholder')) { + drop.element.removeClassName('active'); + } + }); + }, + reset: function(draggable) { + if(this.last_active) this.deactivate(this.last_active, draggable); + } +}); + +/* + Wysija History handling + POTENTIAL FEATURES: + - set a maximum number of items to be stored + +*/ +var WysijaHistory = { + container: 'mailpoet_form_history', + size: 30, + enqueue: function(element) { + // create deep clone (includes child elements) of passed element + var clone = element.clone(true); + + // check if the field is unique + if(parseInt(clone.readAttribute('wysija_unique'), 10) === 1) { + // check if the field is already in the queue + $(WysijaHistory.container).select('[wysija_field="' + clone.readAttribute('wysija_field') + '"]').invoke('remove'); + } + + // check history size + if($(WysijaHistory.container).select('> div').length >= WysijaHistory.size) { + // remove oldest element (last in the list) + $(WysijaHistory.container).select('> div').last().remove(); + } + + // store block in history + $(WysijaHistory.container).insert({ + top: clone + }); + }, + dequeue: function() { + // pop last block off the history + var block = $(WysijaHistory.container).select('div').first(); + + if(block !== undefined) { + // insert block back into the editor + $(WysijaForm.options.body).insert({ + top: block + }); + } + }, + clear: function() { + $(WysijaHistory.container).innerHTML = ''; + }, + remove: function(field) { + $(WysijaHistory.container).select('[wysija_field="' + field + '"]').invoke('remove'); + } +}; + +/* MailPoet Form */ +var WysijaForm = { + version: '0.6', + options: { + container: 'mailpoet_form_container', + editor: 'mailpoet_form_editor', + body: 'mailpoet_form_body', + toolbar: 'mailpoet_form_toolbar', + templates: 'wysija_widget_templates', + debug: false + }, + toolbar: { + effect: null, + x: null, + y: null, + top: null, + left: null + }, + scroll: { + top: 0, + left: 0 + }, + flags: { + doSave: false + }, + locks: { + dragging: false, + selectingColor: false, + showingTools: false + }, + encodeHtmlValue: function(str) { + return str.replace(/&/g, '&').replace(/>/g, '>').replace(/').replace(/</g, '<').replace(/"/g, '"'); + // ": fix for FileMerge because the previous line fucks up its syntax coloring + }, + loading: function(is_loading) { + if(is_loading) { + $(WysijaForm.options.editor).addClassName('loading'); + $(WysijaForm.options.toolbar).addClassName('loading'); + } else { + $(WysijaForm.options.editor).removeClassName('loading'); + $(WysijaForm.options.toolbar).removeClassName('loading'); + } + }, + loadStatic: function(blocks) { + $A(blocks).each(function(block) { + // create block + WysijaForm.Block.create(block, $('block_placeholder')); + }); + }, + load: function(data) { + if(data === undefined) return; + + // load body + if(data.body !== undefined) { + $A(data.body).each(function(block) { + // create block + WysijaForm.Block.create(block, $('block_placeholder')); + }); + + // load settings + var settings_elements = $('mailpoet_form_settings').getElements(); + settings_elements.each(function(setting) { + // skip lists + if(setting.name === 'lists') { + return true; + } else if(setting.name === 'on_success') { + // if the input value is equal to the one stored in the settings + if(setting.value === data.settings[setting.name]) { + // check selected value + $(setting).checked = true; + } + } else if(data.settings[setting.name] !== undefined) { + if(typeof data.settings[setting.name] === 'string') { + setting.setValue(WysijaForm.decodeHtmlValue(data.settings[setting.name])); + } else { + setting.setValue(data.settings[setting.name]); + } + } + }); + } + }, + save: function() { + var position = 1, + data = { + 'settings': $('mailpoet_form_settings').serialize(true), + 'body': [], + 'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null + }; + // body + WysijaForm.getBlocks().each(function(b) { + var block_data = (typeof(b.block['save']) === 'function') ? b.block.save() : null; + + if(block_data !== null) { + // set block position + block_data['position'] = position; + + // increment position + position++; + + // add block data to body + data['body'].push(block_data); + } + }); + + return data; + }, + init: function() { + // set document scroll + info('init -> set scroll offsets'); + WysijaForm.setScrollOffsets(); + + // position toolbar + info('init -> set toolbar position'); + WysijaForm.setToolbarPosition(); + + // enable droppable targets + info('init -> make droppable'); + WysijaForm.makeDroppable(); + + // enable sortable + info('init -> make sortable'); + WysijaForm.makeSortable(); + + // hide controls + info('init -> hide controls'); + WysijaForm.hideControls(); + + // hide settings + info('init -> hide settings'); + WysijaForm.hideSettings(); + + // set settings buttons position + info('init -> init settings'); + WysijaForm.setSettingsPosition(); + + // toggle widgets + info('init -> toggle widgets'); + WysijaForm.toggleWidgets(); + }, + getFieldData: function(element) { + // get basic field data + var data = { + type: element.readAttribute('wysija_type'), + field: element.readAttribute('wysija_field'), + name: element.readAttribute('wysija_name'), + unique: parseInt(element.readAttribute('wysija_unique') || 0, 10), + static: parseInt(element.readAttribute('wysija_static') || 0, 10), + element: element, + params: '' + }; + + // get params (may be empty) + if(element.readAttribute('wysija_params') !== null && element.readAttribute('wysija_params').length > 0) { + data.params = JSON.parse(element.readAttribute('wysija_params')); + } + return data; + }, + toggleWidgets: function() { + $$('a[wysija_unique="1"]').invoke('removeClassName', 'disabled'); + + // loop through each unique field already inserted in the editor and disable its toolbar equivalent + $$('#' + WysijaForm.options.editor + ' [wysija_unique="1"]').each(function(element) { + var field = $$('#' + WysijaForm.options.toolbar + ' [wysija_field="' + element.readAttribute('wysija_field') + '"]').first(); + if(field !== undefined) { + field.addClassName('disabled'); + } + }); + + // hide list selection if a list widget has been dragged into the editor + $('mailpoet_settings_list_selection')[(($$('#' + WysijaForm.options.editor + ' [wysija_field="list"]').length > 0) === true) ? 'hide' : 'show'](); + }, + setBlockPositions: function(event, target) { + // release dragging lock + WysijaForm.locks.dragging = false; + + var index = 1; + WysijaForm.getBlocks().each(function(container) { + container.setPosition(index++); + // remove z-index value to avoid issues when resizing images + if(container['block'] !== undefined) { + container.block.element.setStyle({ + zIndex: '' + }); + } + }); + + if(target !== undefined) { + // get placeholders (previous placeholder matches the placeholder linked to the next block) + var block_placeholder = $(target.element.readAttribute('wysija_placeholder')), + previous_placeholder = target.element.previous('.block_placeholder'); + + if(block_placeholder !== null) { + // put block placeholder before the current block + target.element.insert({ + before: block_placeholder + }); + + // if the next block is a wysija_block, insert previous placeholder + if(target.element.next() !== undefined && target.element.next().hasClassName('mailpoet_form_block') && previous_placeholder !== undefined) { + target.element.insert({ + after: previous_placeholder + }); + } + } + } + }, + setScrollOffsets: function() { + WysijaForm.scroll = document.viewport.getScrollOffsets(); + }, + hideSettings: function() { + $(WysijaForm.options.container).select('.wysija_settings').invoke('hide'); + }, + setSettingsPosition: function() { + // get viewport offsets and dimensions + var viewportHeight = document.viewport.getHeight(), + blockPadding = 5; + + $(WysijaForm.options.container).select('.wysija_settings').each(function(element) { + // get parent dimensions and position + var parentDim = element.up('.mailpoet_form_block').getDimensions(), + parentPos = element.up('.mailpoet_form_block').cumulativeOffset(), + is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight)) ? true : false, + buttonMargin = 5, + relativeTop = buttonMargin; + + if(is_visible) { + // desired position is set to center of viewport + var absoluteTop = parseInt(WysijaForm.scroll.top + ((viewportHeight / 2) - (element.getHeight() / 2)), 10), + parentTop = parseInt(parentPos.top - blockPadding, 10), + parentBottom = parseInt(parentPos.top + parentDim.height - blockPadding, 10); + + // always center + relativeTop = parseInt((parentDim.height / 2) - (element.getHeight() / 2), 10); + } + // set position for button + $(element).setStyle({ + left: parseInt((parentDim.width / 2) - (element.getWidth() / 2)) + 'px', + top: relativeTop + 'px' + }); + }); + }, + initToolbarPosition: function() { + if(WysijaForm.toolbar.top === null) WysijaForm.toolbar.top = parseInt($(WysijaForm.options.container).positionedOffset().top); + if(WysijaForm.toolbar.y === null) WysijaForm.toolbar.y = parseInt(WysijaForm.toolbar.top); + + if(isRtl) { + if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = 0; + } else { + if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = parseInt($(WysijaForm.options.container).positionedOffset().left); + } + if(WysijaForm.toolbar.x === null) WysijaForm.toolbar.x = parseInt(WysijaForm.toolbar.left + $(WysijaForm.options.container).getDimensions().width + 15); + + }, + setToolbarPosition: function() { + WysijaForm.initToolbarPosition(); + + var position = { + top: WysijaForm.toolbar.y + 'px', + visibility: 'visible' + }; + + if(isRtl) { + position.right = WysijaForm.toolbar.x + 'px'; + } else { + position.left = WysijaForm.toolbar.x + 'px'; + } + + $(WysijaForm.options.toolbar).setStyle(position); + }, + updateToolbarPosition: function() { + // init toolbar position (updates scroll and toolbar y) + WysijaForm.initToolbarPosition(); + + // cancel previous effect + if(WysijaForm.toolbar.effect !== null) WysijaForm.toolbar.effect.cancel(); + + if(WysijaForm.scroll.top >= (WysijaForm.toolbar.top - 20)) { + WysijaForm.toolbar.y = parseInt(20 + WysijaForm.scroll.top); + // start effect + WysijaForm.toolbar.effect = new Effect.Move(WysijaForm.options.toolbar, { + x: WysijaForm.toolbar.x, + y: WysijaForm.toolbar.y, + mode: 'absolute', + duration: 0.2 + }); + } else { + $(WysijaForm.options.toolbar).setStyle({ + left: WysijaForm.toolbar.x + 'px', + top: WysijaForm.toolbar.top + 'px' + }); + } + }, + blockDropOptions: { + accept: $w('mailpoet_form_field'), // acceptable items (classes array) + onEnter: function(draggable, droppable) { + $(droppable).addClassName('hover'); + }, + onLeave: function(draggable, droppable) { + $(droppable).removeClassName('hover'); + }, + onDrop: function(draggable, droppable) { + // custom data for images + droppable.fire('wjfe:item:drop', WysijaForm.getFieldData(draggable)); + $(droppable).removeClassName('hover'); + } + }, + hideControls: function() { + try { + return WysijaForm.getBlocks().invoke('hideControls'); + } catch(e) { + return; + } + }, + hideTools: function() { + $$('.wysija_tools').invoke('hide'); + WysijaForm.locks.showingTools = false; + }, + instances: {}, + get: function(element, type) { + if(type === undefined) type = 'block'; + // identify element + var id = element.identify(); + var instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id); + + WysijaForm.instances[id] = instance; + return instance; + }, + makeDroppable: function() { + Droppables.add('block_placeholder', WysijaForm.blockDropOptions); + }, + makeSortable: function() { + var body = $(WysijaForm.options.body); + Sortable.create(body, { + tag: 'div', + only: 'mailpoet_form_block', + scroll: window, + handle: 'handle', + constraint: 'vertical' + + }); + Draggables.removeObserver(body); + Draggables.addObserver({ + element: body, + onStart: WysijaForm.startBlockPositions, + onEnd: WysijaForm.setBlockPositions + }); + }, + hideBlockControls: function() { + $$('.wysija_controls').invoke('hide'); + this.getBlockElements().invoke('removeClassName', 'hover'); + }, + getBlocks: function() { + return WysijaForm.getBlockElements().map(function(element) { + return WysijaForm.get(element); + }); + }, + getBlockElements: function() { + return $(WysijaForm.options.container).select('.mailpoet_form_block'); + }, + startBlockPositions: function(event, target) { + if(target.element.hasClassName('mailpoet_form_block')) { + // store block placeholder id for the block that is being repositionned + if(target.element.previous('.block_placeholder') !== undefined) { + target.element.writeAttribute('wysija_placeholder', target.element.previous('.block_placeholder').identify()); + } + } + WysijaForm.locks.dragging = true; + }, + encodeURIComponent: function(str) { + // check if it's a url and if so, prevent encoding of protocol + var regexp = new RegExp(/^http[s]?:\/\//), + protocol = regexp.exec(str); + + if(protocol === null) { + // this is not a url so encode the whole thing + return encodeURIComponent(str).replace(/[!'()*]/g, escape); + } else if(protocol.length === 1) { + // this is a url, so do not encode the protocol + return encodeURI(str).replace(/[!'()*]/g, escape); + } + } +}; + +WysijaForm.DraggableItem = Class.create({ + initialize: function(element) { + this.elementType = $(element).readAttribute('wysija_type'); + this.element = $(element).down() || $(element); + this.clone = this.cloneElement(); + this.insert(); + }, + STYLES: new Template('position: absolute; top: #{top}px; left: #{left}px;'), + cloneElement: function() { + var clone = this.element.clone(), + offset = this.element.cumulativeOffset(), + list = this.getList(), + styles = this.STYLES.evaluate({ + top: offset.top - list.scrollTop, + left: offset.left - list.scrollLeft + }); + clone.setStyle(styles); + + clone.addClassName('mailpoet_form_widget'); + clone.addClassName(this.elementType); + clone.innerHTML = this.element.innerHTML; + return clone; + }, + getOffset: function() { + return this.element.offsetTop - this.getList().scrollTop; + }, + getList: function() { + return this.element.up('ul'); + }, + insert: function() { + $$("body")[0].insert(this.clone); + }, + onMousedown: function(event) { + var draggable = new Draggable(this.clone, { + scroll: window, + onStart: function() { + Droppables.displayArea(draggable); + }, + onEnd: function(drag) { + drag.destroy(); + drag.element.remove(); + Droppables.hideArea(); + }, + starteffect: function(element) { + new Effect.Opacity(element, { + duration: 0.2, + from: element.getOpacity(), + to: 0.7 + }); + }, + endeffect: Prototype.emptyFunction + }); + draggable.initDrag(event); + draggable.startDrag(event); + return draggable; + } +}); +Object.extend(WysijaForm.DraggableItem, Observable).observe('a[class="mailpoet_form_field"]'); + + +WysijaForm.Block = Class.create({ + /* Invoked on load */ + initialize: function(element) { + info('block -> init'); + + this.element = $(element); + this.block = new WysijaForm.Widget(this.element); + + // enable block placeholder + this.block.makeBlockDroppable(); + + // setup events + if(this.block['setup'] !== undefined) { + this.block.setup(); + } + return this; + }, + setPosition: function(position) { + this.element.writeAttribute('wysija_position', position); + }, + hideControls: function() { + if(this['getControls']) { + this.element.removeClassName('hover'); + this.getControls().hide(); + } + }, + showControls: function() { + if(this['getControls']) { + this.element.addClassName('hover'); + try { + this.getControls().show(); + } catch(e) {; + } + } + }, + makeBlockDroppable: function() { + if(this.isBlockDroppableEnabled() === false) { + var block_placeholder = this.getBlockDroppable(); + Droppables.add(block_placeholder.identify(), WysijaForm.blockDropOptions); + block_placeholder.addClassName('enabled'); + } + }, + removeBlockDroppable: function() { + if(this.isBlockDroppableEnabled()) { + var block_placeholder = this.getBlockDroppable(); + Droppables.remove(block_placeholder.identify()); + block_placeholder.removeClassName('enabled'); + } + }, + isBlockDroppableEnabled: function() { + // if the block_placeholder does not exist, create it + var block_placeholder = this.getBlockDroppable(); + if(block_placeholder === null) { + return this.createBlockDroppable().hasClassName('enabled'); + } else { + return block_placeholder.hasClassName('enabled'); + } + }, + createBlockDroppable: function() { + info('block -> createBlockDroppable'); + this.element.insert({ + before: '