Browse Source

First integration of scripts inside Components

pull/67/head
Artur Arseniev 9 years ago
parent
commit
9cd774f9d8
  1. 293
      src/canvas/view/CanvasView.js
  2. 37
      src/demo.js
  3. 3
      src/dom_components/model/Component.js
  4. 569
      src/dom_components/view/ComponentView.js

293
src/canvas/view/CanvasView.js

@ -1,22 +1,22 @@
define(['backbone','./FrameView'],
function(Backbone, FrameView) {
/**
* @class CanvasView
* */
return Backbone.View.extend({
/**
* @class CanvasView
* */
return Backbone.View.extend({
initialize: function(o) {
initialize: function(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff');
this.config = o.config || {};
this.config = o.config || {};
this.em = this.config.em || {};
this.ppfx = this.config.pStylePrefix || '';
this.className = this.config.stylePrefix + 'canvas';
this.listenTo(this.em, 'change:canvasOffset', this.clearOff);
this.frame = new FrameView({
this.ppfx = this.config.pStylePrefix || '';
this.className = this.config.stylePrefix + 'canvas';
this.listenTo(this.em, 'change:canvasOffset', this.clearOff);
this.frame = new FrameView({
model: this.model.get('frame'),
config: this.config
});
},
},
/**
* Update tools position
@ -27,36 +27,38 @@ function(Backbone, FrameView) {
var body = this.frame.el.contentDocument.body;
this.toolsEl.style.top = '-' + body.scrollTop + u;
this.toolsEl.style.left = '-' + body.scrollLeft + u;
this.em.trigger('canvasScroll');
this.em.trigger('canvasScroll');
},
/**
* Render inside frame's body
* @private
*/
renderBody: function(){
var wrap = this.model.get('frame').get('wrapper');
if(wrap){
var ppfx = this.ppfx;
/**
* Render inside frame's body
* @private
*/
renderBody: function(){
var wrap = this.model.get('frame').get('wrapper');
var em = this.config.em;
if(wrap) {
var ppfx = this.ppfx;
var body = this.frame.$el.contents().find('body');
var cssc = this.config.em.get('CssComposer');
var conf = this.config.em.get('Config');
var cssc = em.get('CssComposer');
var conf = em.get('Config');
body.append(wrap.render()).append(cssc.render());
var protCss = conf.protectedCss;
var frameCss = '.' + ppfx + 'dashed :not([contenteditable]) > *{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
'.' + ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + ppfx + 'no-select{user-select: none; -webkit-user-select:none; -moz-user-select: none}'+
'.' + ppfx + 'freezed{opacity: 0.5; pointer-events: none}' +
'.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}' +
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
if(protCss)
body.append('<style>' + frameCss + protCss + '</style>');
this.config.em.trigger('loaded');
body.append('<style>' + frameCss + protCss + '</style>');
body.append(this.getJsContainer());
em.trigger('loaded');
this.frame.el.contentWindow.onscroll = this.onFrameScroll;
this.frame.udpateOffset();
@ -71,7 +73,7 @@ function(Backbone, FrameView) {
doc.dispatchEvent(new KeyboardEvent(e.type, e));
});
}
},
},
/**
* Get the offset of the element
@ -87,111 +89,146 @@ function(Backbone, FrameView) {
};
},
/**
* Cleare cached offsets
* @private
*/
clearOff: function(){
this.frmOff = null;
this.cvsOff = null;
},
/**
* Return frame offset
* @return {Object}
* @private
*/
getFrameOffset: function () {
if(!this.frmOff)
this.frmOff = this.offset(this.frame.el);
return this.frmOff;
},
/**
* Return canvas offset
* @return {Object}
* @private
*/
getCanvasOffset: function () {
if(!this.cvsOff)
this.cvsOff = this.offset(this.el);
return this.cvsOff;
},
/**
* Returns element's data info
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementPos: function(el) {
var frmOff = this.getFrameOffset();
var cvsOff = this.getCanvasOffset();
var eo = this.offset(el);
var top = eo.top + frmOff.top - cvsOff.top;
var left = eo.left + frmOff.left - cvsOff.left;
return {
top: top,
left: left,
height: el.offsetHeight,
width: el.offsetWidth
};
},
/**
* Returns position data of the canvas element
* @return {Object} obj Position object
*/
getPosition: function() {
var bEl = this.frame.el.contentDocument.body;
var fo = this.getFrameOffset();
var co = this.getCanvasOffset();
return {
top: fo.top + bEl.scrollTop - co.top,
left: fo.left + bEl.scrollLeft - co.left
};
},
render: function() {
this.wrapper = this.model.get('wrapper');
if(this.wrapper && typeof this.wrapper.render == 'function'){
this.model.get('frame').set('wrapper', this.wrapper);
this.$el.append(this.frame.render().el);
var frame = this.frame;
frame.el.onload = this.renderBody;
}
var ppfx = this.ppfx;
toolsEl = $('<div>', { id: ppfx + 'tools' }).get(0);
this.hlEl = $('<div>', { class: ppfx + 'highlighter' }).get(0);
this.badgeEl = $('<div>', {class: ppfx + 'badge'}).get(0);
this.placerEl = $('<div>', {class: ppfx + 'placeholder'}).get(0);
this.placerIntEl = $('<div>', {class: ppfx + 'placeholder-int'}).get(0);
this.ghostEl = $('<div>', {class: ppfx + 'ghost'}).get(0);
this.toolbarEl = $('<div>', {class: ppfx + 'toolbar'}).get(0);
this.resizerEl = $('<div>', {class: ppfx + 'resizer'}).get(0);
/**
* Cleare cached offsets
* @private
*/
clearOff: function(){
this.frmOff = null;
this.cvsOff = null;
},
/**
* Return frame offset
* @return {Object}
* @private
*/
getFrameOffset: function () {
if(!this.frmOff)
this.frmOff = this.offset(this.frame.el);
return this.frmOff;
},
/**
* Return canvas offset
* @return {Object}
* @private
*/
getCanvasOffset: function () {
if(!this.cvsOff)
this.cvsOff = this.offset(this.el);
return this.cvsOff;
},
/**
* Returns element's data info
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementPos: function(el) {
var frmOff = this.getFrameOffset();
var cvsOff = this.getCanvasOffset();
var eo = this.offset(el);
var top = eo.top + frmOff.top - cvsOff.top;
var left = eo.left + frmOff.left - cvsOff.left;
return {
top: top,
left: left,
height: el.offsetHeight,
width: el.offsetWidth
};
},
/**
* Returns position data of the canvas element
* @return {Object} obj Position object
* @private
*/
getPosition: function() {
var bEl = this.frame.el.contentDocument.body;
var fo = this.getFrameOffset();
var co = this.getCanvasOffset();
return {
top: fo.top + bEl.scrollTop - co.top,
left: fo.left + bEl.scrollLeft - co.left
};
},
/**
* Update javascript of a specific component passed by its View
* @param {View} view Component's View
* @private
*/
updateScript: function(view) {
var scriptsContainer = this.getJsContainer();
if(!view.scriptContainer) {
view.scriptContainer = $('<div>');
scriptsContainer.append(view.scriptContainer.get(0));
}
var id = view.model.cid;
var script = view.model.get('script');
view.el.id = id;
view.scriptContainer.html('');
view.scriptContainer.append('<script>'+
'var item = document.getElementById("'+id+'");'+
'(function(){' + script + '}.bind(item))()</script>');
},
/**
* Get javascript container
* @private
*/
getJsContainer: function () {
if (!this.jsContainer) {
this.jsContainer = $('<div>', {class: this.ppfx + 'js-cont'}).get(0);
}
return this.jsContainer;
},
render: function() {
this.wrapper = this.model.get('wrapper');
if(this.wrapper && typeof this.wrapper.render == 'function'){
this.model.get('frame').set('wrapper', this.wrapper);
this.$el.append(this.frame.render().el);
var frame = this.frame;
frame.el.onload = this.renderBody;
}
var ppfx = this.ppfx;
toolsEl = $('<div>', { id: ppfx + 'tools' }).get(0);
this.hlEl = $('<div>', { class: ppfx + 'highlighter' }).get(0);
this.badgeEl = $('<div>', {class: ppfx + 'badge'}).get(0);
this.placerEl = $('<div>', {class: ppfx + 'placeholder'}).get(0);
this.placerIntEl = $('<div>', {class: ppfx + 'placeholder-int'}).get(0);
this.ghostEl = $('<div>', {class: ppfx + 'ghost'}).get(0);
this.toolbarEl = $('<div>', {class: ppfx + 'toolbar'}).get(0);
this.resizerEl = $('<div>', {class: ppfx + 'resizer'}).get(0);
this.offsetEl = $('<div>', {class: ppfx + 'offset-v'}).get(0);
this.fixedOffsetEl = $('<div>', {class: ppfx + 'offset-fixed-v'}).get(0);
this.placerEl.appendChild(this.placerIntEl);
toolsEl.appendChild(this.hlEl);
toolsEl.appendChild(this.badgeEl);
toolsEl.appendChild(this.placerEl);
toolsEl.appendChild(this.ghostEl);
toolsEl.appendChild(this.toolbarEl);
toolsEl.appendChild(this.resizerEl);
this.placerEl.appendChild(this.placerIntEl);
toolsEl.appendChild(this.hlEl);
toolsEl.appendChild(this.badgeEl);
toolsEl.appendChild(this.placerEl);
toolsEl.appendChild(this.ghostEl);
toolsEl.appendChild(this.toolbarEl);
toolsEl.appendChild(this.resizerEl);
toolsEl.appendChild(this.offsetEl);
toolsEl.appendChild(this.fixedOffsetEl);
this.$el.append(toolsEl);
this.$el.append(toolsEl);
var rte = this.em.get('rte');
if(rte)
toolsEl.appendChild(rte.render());
this.toolsEl = toolsEl;
this.$el.attr({class: this.className});
return this;
},
this.$el.attr({class: this.className});
return this;
},
});
});
});

37
src/demo.js

@ -12,9 +12,40 @@ require(['config/require-config'], function() {
noticeOnUnload: 0,
container : '#gjs',
height: '100%',
fromElement: true,
/*
//fromElement: true,
components: [{
script: 'this.innerHTML= "test1";',
style: {
background: 'red',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
script: 'this.innerHTML= "test1";',
style: {
background: 'blue',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
script: 'this.innerHTML= "test2";',
style: {
background: 'green',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
style: {
background: 'yellow',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
type: 'text',
style:{
width:'100px',
@ -35,7 +66,7 @@ require(['config/require-config'], function() {
type: 'textnode',
content: " More text node --- ",
}],
}],*/
}],
storageManager:{
autoload: 0,

3
src/dom_components/model/Component.js

@ -59,6 +59,9 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
// Array of classes
classes: '',
// Component's javascript
script: '',
// Traits
traits: ['id', 'title'],

569
src/dom_components/view/ComponentView.js

@ -1,280 +1,301 @@
define(['backbone', './ComponentsView'],
function (Backbone, ComponentsView) {
return Backbone.View.extend({
events: {
'click': 'initResize',
},
className : function(){
return this.getClasses();
},
tagName: function(){
return this.model.get('tagName');
},
initialize: function(opt){
this.opts = opt || {};
this.config = opt.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.components = this.model.get('components');
this.attr = this.model.get("attributes");
this.classe = this.attr.class || [];
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'change:attributes', this.updateAttributes);
this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:state', this.updateState);
this.listenTo(this.model.get('classes'), 'add remove change', this.updateClasses);
this.$el.data('model', this.model);
this.model.view = this;
this.$el.data("collection", this.components);
if(this.model.get('classes').length)
this.importClasses();
this.init();
},
/**
function (Backbone, ComponentsView) {
return Backbone.View.extend({
events: {
'click': 'initResize',
},
className : function(){
return this.getClasses();
},
tagName: function(){
return this.model.get('tagName');
},
initialize: function(opt) {
this.opts = opt || {};
this.config = this.opts.config || {};
this.em = this.config.em || '';
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.components = this.model.get('components');
this.attr = this.model.get("attributes");
this.classe = this.attr.class || [];
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'change:attributes', this.updateAttributes);
this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:state', this.updateState);
this.listenTo(this.model, 'change:script', this.render);
this.listenTo(this.model.get('classes'), 'add remove change', this.updateClasses);
this.$el.data('model', this.model);
this.model.view = this;
this.$el.data("collection", this.components);
if(this.model.get('classes').length)
this.importClasses();
this.init();
},
/**
* Initialize callback
*/
init: function () {},
/**
* Import, if possible, classes inside main container
* @private
* */
importClasses: function(){
var clm = this.config.em.get('SelectorManager');
if(clm){
this.model.get('classes').each(function(m){
clm.add(m.get('name'));
});
}
},
/**
* Fires on state update. If the state is not empty will add a helper class
* @param {Event} e
* @private
* */
updateState: function(e){
var cl = 'hc-state';
var state = this.model.get('state');
if(state){
this.$el.addClass(cl);
}else{
this.$el.removeClass(cl);
}
},
/**
* Update item on status change
* @param {Event} e
* @private
* */
updateStatus: function(e){
var s = this.model.get('status'),
pfx = this.pfx;
switch(s) {
case 'selected':
this.$el.addClass(pfx + 'selected');
break;
case 'moving':
break;
default:
this.$el.removeClass(pfx + 'selected');
}
},
/**
* Get classes from attributes.
* This method is called before initialize
*
* @return {Array}|null
* @private
* */
getClasses: function(){
var attr = this.model.get("attributes"),
classes = attr['class'] || [];
if(classes.length){
return classes.join(" ");
}else
return null;
},
/**
* Update attributes
* @private
* */
updateAttributes: function(){
var attributes = {},
attr = this.model.get("attributes");
for(var key in attr) {
if(attr.hasOwnProperty(key))
attributes[key] = attr[key];
}
// Update src
if(this.model.get("src"))
attributes.src = this.model.get("src");
var styleStr = this.getStyleString();
if(styleStr)
attributes.style = styleStr;
this.$el.attr(attributes);
},
/**
* Update style attribute
* @private
* */
updateStyle: function(){
this.$el.attr('style', this.getStyleString());
},
/**
* Return style string
* @return {string}
* @private
* */
getStyleString: function(){
var style = '';
this.style = this.model.get('style');
for(var key in this.style) {
if(this.style.hasOwnProperty(key))
style += key + ':' + this.style[key] + ';';
}
return style;
},
/**
* Update classe attribute
* @private
* */
updateClasses: function(){
var str = '';
this.model.get('classes').each(function(model){
str += model.get('name') + ' ';
});
str = str.trim();
if(str)
this.$el.attr('class', str);
else
this.$el.removeAttr('class');
// Regenerate status class
this.updateStatus();
},
/**
* Reply to event call
* @param object Event that generated the request
* @private
* */
eventCall: function(event){
event.viewResponse = this;
},
/**
* Init component for resizing
*/
initResize: function () {
var em = this.opts.config.em;
var editor = em ? em.get('Editor') : '';
var config = em ? em.get('Config') : '';
var pfx = config.stylePrefix || '';
var attrName = 'data-' + pfx + 'handler';
var resizeClass = pfx + 'resizing';
var model = this.model;
var modelToStyle;
var toggleBodyClass = function(method, e, opts) {
var handlerAttr = e.target.getAttribute(attrName);
var resizeHndClass = pfx + 'resizing-' + handlerAttr;
var classToAdd = resizeClass;// + ' ' +resizeHndClass;
if (opts.docs) {
opts.docs.find('body')[method](classToAdd);
}
};
if(editor && this.model.get('resizable')) {
editor.runCommand('resize', {
el: this.el,
options: {
onStart: function (e, opts) {
toggleBodyClass('addClass', e, opts);
modelToStyle = em.get('StyleManager').getModelToStyle(model);
},
// Update all positioned elements (eg. component toolbar)
onMove: function () {
editor.trigger('change:canvasOffset');
},
onEnd: function (e, opts) {
toggleBodyClass('removeClass', e, opts);
editor.trigger('change:canvasOffset');
},
updateTarget: function(el, rect, store) {
if (!modelToStyle) {
return;
}
var unit = 'px';
var style = _.clone(modelToStyle.get('style'));
var width = rect.w + (store ? 1 : 0);
style.width = width + unit;
style.height = rect.h + unit;
modelToStyle.set('style', style, {avoidStore: 1});
em.trigger('targetStyleUpdated');
// This trick will trigger the Undo Manager. To trigger "change:style"
// on the Model you need to provide a new object and after that
// Undo Manager will trigger only if values are different (this is why
// above I've added + 1 to width if store required)
if(store) {
var style3 = _.clone(style);
style3.width = (width - 1) + unit;
modelToStyle.set('style', style3);
}
}
}
});
}
},
/**
* Prevent default helper
* @param {Event} e
* @private
*/
prevDef: function (e) {
e.preventDefault();
},
render: function() {
this.updateAttributes();
this.updateClasses();
this.$el.html(this.model.get('content'));
var view = new ComponentsView({
collection: this.model.get('components'),
config: this.config,
defaultTypes: this.opts.defaultTypes,
componentTypes: this.opts.componentTypes,
});
// With childNodes lets avoid wrapping 'div'
this.$el.append(view.render(this.$el).el.childNodes);
return this;
},
});
/**
* Import, if possible, classes inside main container
* @private
* */
importClasses: function(){
var clm = this.config.em.get('SelectorManager');
if(clm){
this.model.get('classes').each(function(m){
clm.add(m.get('name'));
});
}
},
/**
* Fires on state update. If the state is not empty will add a helper class
* @param {Event} e
* @private
* */
updateState: function(e){
var cl = 'hc-state';
var state = this.model.get('state');
if(state){
this.$el.addClass(cl);
}else{
this.$el.removeClass(cl);
}
},
/**
* Update item on status change
* @param {Event} e
* @private
* */
updateStatus: function(e){
var s = this.model.get('status'),
pfx = this.pfx;
switch(s) {
case 'selected':
this.$el.addClass(pfx + 'selected');
break;
case 'moving':
break;
default:
this.$el.removeClass(pfx + 'selected');
}
},
/**
* Get classes from attributes.
* This method is called before initialize
*
* @return {Array}|null
* @private
* */
getClasses: function(){
var attr = this.model.get("attributes"),
classes = attr['class'] || [];
if(classes.length){
return classes.join(" ");
}else
return null;
},
/**
* Update attributes
* @private
* */
updateAttributes: function(){
var attributes = {},
attr = this.model.get("attributes");
for(var key in attr) {
if(attr.hasOwnProperty(key))
attributes[key] = attr[key];
}
// Update src
if(this.model.get("src"))
attributes.src = this.model.get("src");
var styleStr = this.getStyleString();
if(styleStr)
attributes.style = styleStr;
this.$el.attr(attributes);
},
/**
* Update style attribute
* @private
* */
updateStyle: function(){
this.$el.attr('style', this.getStyleString());
},
/**
* Return style string
* @return {string}
* @private
* */
getStyleString: function(){
var style = '';
this.style = this.model.get('style');
for(var key in this.style) {
if(this.style.hasOwnProperty(key))
style += key + ':' + this.style[key] + ';';
}
return style;
},
/**
* Update classe attribute
* @private
* */
updateClasses: function(){
var str = '';
this.model.get('classes').each(function(model){
str += model.get('name') + ' ';
});
str = str.trim();
if(str)
this.$el.attr('class', str);
else
this.$el.removeAttr('class');
// Regenerate status class
this.updateStatus();
},
/**
* Reply to event call
* @param object Event that generated the request
* @private
* */
eventCall: function(event){
event.viewResponse = this;
},
/**
* Init component for resizing
*/
initResize: function () {
var em = this.opts.config.em;
var editor = em ? em.get('Editor') : '';
var config = em ? em.get('Config') : '';
var pfx = config.stylePrefix || '';
var attrName = 'data-' + pfx + 'handler';
var resizeClass = pfx + 'resizing';
var model = this.model;
var modelToStyle;
var toggleBodyClass = function(method, e, opts) {
var handlerAttr = e.target.getAttribute(attrName);
var resizeHndClass = pfx + 'resizing-' + handlerAttr;
var classToAdd = resizeClass;// + ' ' +resizeHndClass;
if (opts.docs) {
opts.docs.find('body')[method](classToAdd);
}
};
if(editor && this.model.get('resizable')) {
editor.runCommand('resize', {
el: this.el,
options: {
onStart: function (e, opts) {
toggleBodyClass('addClass', e, opts);
modelToStyle = em.get('StyleManager').getModelToStyle(model);
},
// Update all positioned elements (eg. component toolbar)
onMove: function () {
editor.trigger('change:canvasOffset');
},
onEnd: function (e, opts) {
toggleBodyClass('removeClass', e, opts);
editor.trigger('change:canvasOffset');
},
updateTarget: function(el, rect, store) {
if (!modelToStyle) {
return;
}
var unit = 'px';
var style = _.clone(modelToStyle.get('style'));
var width = rect.w + (store ? 1 : 0);
style.width = width + unit;
style.height = rect.h + unit;
modelToStyle.set('style', style, {avoidStore: 1});
em.trigger('targetStyleUpdated');
// This trick will trigger the Undo Manager. To trigger "change:style"
// on the Model you need to provide a new object and after that
// Undo Manager will trigger only if values are different (this is why
// above I've added + 1 to width if store required)
if(store) {
var style3 = _.clone(style);
style3.width = (width - 1) + unit;
modelToStyle.set('style', style3);
}
}
}
});
}
},
/**
* Prevent default helper
* @param {Event} e
* @private
*/
prevDef: function (e) {
e.preventDefault();
},
/**
* Render component's script
* @private
*/
updateScript: function () {
var em = this.em;
if(em) {
var canvas = em.get('Canvas');
canvas.getCanvasView().updateScript(this);
}
},
render: function() {
var model = this.model;
this.updateAttributes();
this.updateClasses();
this.$el.html(this.model.get('content'));
var view = new ComponentsView({
collection: this.model.get('components'),
config: this.config,
defaultTypes: this.opts.defaultTypes,
componentTypes: this.opts.componentTypes,
});
// With childNodes lets avoid wrapping 'div'
this.$el.append(view.render(this.$el).el.childNodes);
// Render script
if(model.get('script')) {
this.updateScript();
}
return this;
},
});
});

Loading…
Cancel
Save