mirror of https://github.com/artf/grapesjs.git
19 changed files with 26 additions and 603 deletions
@ -1,24 +0,0 @@ |
|||
define(function () { |
|||
return { |
|||
|
|||
// Style prefix
|
|||
stylePrefix : 'clm-', |
|||
|
|||
// Default classes
|
|||
defaults : [], |
|||
|
|||
// Label for classes
|
|||
label: 'Classes', |
|||
|
|||
// Label for states
|
|||
statesLabel: '- State -', |
|||
|
|||
// States
|
|||
states: [ |
|||
{ name: 'hover', label: 'Hover' }, |
|||
{ name: 'active', label: 'Click' }, |
|||
{ name: 'nth-of-type(2n)', label: 'Even/Odd' } |
|||
], |
|||
|
|||
}; |
|||
}); |
|||
@ -1,90 +0,0 @@ |
|||
/** |
|||
* * [add](#add) |
|||
* * [get](#get) |
|||
* * [getAll](#getall) |
|||
* * [remove](#remove) |
|||
* * [store](#store) |
|||
* * [load](#load) |
|||
* * [render](#render) |
|||
* |
|||
* Before using methods you should get first the module from the editor instance, in this way: |
|||
* |
|||
* ```js
|
|||
* var classManager = editor.ClassManager; |
|||
* ``` |
|||
* |
|||
* @module ClassManager |
|||
*/ |
|||
define(function(require) { |
|||
|
|||
return function(config) { |
|||
var c = config || {}, |
|||
def = require('./config/config'); |
|||
ClassTags = require('./model/ClassTags'); |
|||
ClassTagsView = require('./view/ClassTagsView'); |
|||
|
|||
for (var name in def) { |
|||
if (!(name in c)) |
|||
c[name] = def[name]; |
|||
} |
|||
|
|||
classes = new ClassTags(c.defaults); |
|||
config = c; |
|||
|
|||
return { |
|||
|
|||
config: config, |
|||
|
|||
ClassTags: ClassTags, |
|||
|
|||
ClassTagsView: ClassTagsView, |
|||
|
|||
/** |
|||
* Name of the module |
|||
* @type {String} |
|||
* @private |
|||
*/ |
|||
name: 'ClassManager', |
|||
|
|||
/** |
|||
* Indicates if module is public |
|||
* @type {Boolean} |
|||
* @private |
|||
*/ |
|||
public: true, |
|||
|
|||
/** |
|||
* Add new class to collection only if it's not already exists |
|||
* @param {String} name Class name |
|||
* @return {Model} |
|||
* */ |
|||
addClass: function(name){ |
|||
var label = name; |
|||
var c = this.getClass(name); |
|||
if(!c) |
|||
return classes.add({name: name, label: label}); |
|||
return c; |
|||
}, |
|||
|
|||
/** |
|||
* Get class by its name |
|||
* @param {String} id Class name |
|||
* |
|||
* @return {Object|null} |
|||
* */ |
|||
getClass: function(id) { |
|||
return classes.where({name: id})[0]; |
|||
}, |
|||
|
|||
/** |
|||
* Get the collection of classes |
|||
* @return {Collection} |
|||
* */ |
|||
getAllClasses: function() { |
|||
return classes; |
|||
}, |
|||
|
|||
}; |
|||
}; |
|||
|
|||
}); |
|||
@ -1,26 +0,0 @@ |
|||
define(['backbone'], |
|||
function (Backbone) { |
|||
return Backbone.Model.extend({ |
|||
|
|||
defaults: { |
|||
label: '', |
|||
name: '', |
|||
active: true, |
|||
}, |
|||
|
|||
initialize: function(){ |
|||
this.set('name', this.escapeName(this.get('name'))); |
|||
}, |
|||
|
|||
/** |
|||
* Escape string |
|||
* @param {string} name |
|||
* |
|||
* @return {string} |
|||
*/ |
|||
escapeName: function(name) { |
|||
return name.toLowerCase().replace(/([^a-z0-9\w]+)/gi, '-'); |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -1,11 +0,0 @@ |
|||
define(['backbone','./ClassTag'], |
|||
function (Backbone, ClassTag) { |
|||
/** |
|||
* @class ClassTags |
|||
* */ |
|||
return Backbone.Collection.extend({ |
|||
|
|||
model: ClassTag, |
|||
|
|||
}); |
|||
}); |
|||
@ -1,5 +0,0 @@ |
|||
<span id="<%= pfx %>checkbox" class="fa"></span> |
|||
<span id="<%= pfx %>tag-label"> |
|||
<input class="<%= ppfx %>no-app" value="<%= label %>" <%= inputProp %>/> |
|||
</span> |
|||
<span id="<%= pfx %>close">⨯</span> |
|||
@ -1,27 +0,0 @@ |
|||
<div id="<%= pfx %>up"> |
|||
<div id="<%= pfx %>label"><%= label %></div> |
|||
<div id="<%= pfx %>status-c"> |
|||
<span id="<%= pfx %>input-c"> |
|||
<div class="<%= pfx %>field <%= pfx %>select"> |
|||
<span id="<%= pfx %>input-holder"> |
|||
<select id="<%= pfx %>states"> |
|||
<option value=""><%= statesLabel %></option> |
|||
</select> |
|||
</span> |
|||
<div class="<%= pfx %>sel-arrow"> |
|||
<div class="<%= pfx %>d-s-arrow"></div> |
|||
</div> |
|||
</div> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
<div id="<%= pfx %>tags-field"> |
|||
<div id="<%= pfx %>tags-c"></div> |
|||
<input id="<%= pfx %>new" /> |
|||
<span id="<%= pfx %>add-tag" class="fa fa-plus"></span> |
|||
</div> |
|||
<div id="<%= pfx %>sel-help"> |
|||
<div id="<%= pfx %>label">Selected</div> |
|||
<div id="<%= pfx %>sel"></div> |
|||
<div style="clear:both"></div> |
|||
</div> |
|||
@ -1,129 +0,0 @@ |
|||
define(['backbone', 'text!./../template/classTag.html'], |
|||
function (Backbone, tagTemplate) { |
|||
/** |
|||
* @class ClassTagView |
|||
* */ |
|||
return Backbone.View.extend({ |
|||
|
|||
template: _.template(tagTemplate), |
|||
|
|||
events: {}, |
|||
|
|||
initialize: function(o) { |
|||
this.config = o.config || {}; |
|||
this.coll = o.coll || null; |
|||
this.pfx = this.config.stylePrefix || ''; |
|||
this.ppfx = this.config.pStylePrefix || ''; |
|||
this.inputProp = 'readonly'; |
|||
this.target = this.config.target; |
|||
this.className = this.pfx + 'tag'; |
|||
this.closeId = this.pfx + 'close'; |
|||
this.chkId = this.pfx + 'checkbox'; |
|||
this.labelId = this.pfx + 'tag-label'; |
|||
this.events['click #' + this.closeId ] = 'removeTag'; |
|||
this.events['click #' + this.chkId ] = 'changeStatus'; |
|||
this.events['dblclick #' + this.labelId ] = 'startEditTag'; |
|||
this.events['keypress #' + this.labelId + ' input'] = 'updateInputLabel'; |
|||
this.events['blur #' + this.labelId + ' input'] = 'endEditTag'; |
|||
|
|||
this.listenTo( this.model, 'change:active', this.updateStatus); |
|||
this.delegateEvents(); |
|||
}, |
|||
|
|||
/** |
|||
* Start editing tag |
|||
*/ |
|||
startEditTag: function(){ |
|||
this.$labelInput.prop(this.inputProp, false); |
|||
}, |
|||
|
|||
/** |
|||
* End editing tag. If the class typed already exists the |
|||
* old one will be restored otherwise will be changed |
|||
*/ |
|||
endEditTag: function(){ |
|||
var value = this.$labelInput.val(); |
|||
var next = this.model.escapeName(value); |
|||
|
|||
if(this.target){ |
|||
var clsm = this.target.ClassManager; |
|||
|
|||
if(clsm){ |
|||
if(clsm.getClass(next)) |
|||
this.$labelInput.val(this.model.get('label')); |
|||
else |
|||
this.model.set({ name: next, label: value}); |
|||
} |
|||
} |
|||
this.$labelInput.prop(this.inputProp, true); |
|||
}, |
|||
|
|||
/** |
|||
* Update status of the tag |
|||
*/ |
|||
changeStatus: function(){ |
|||
this.model.set('active', !this.model.get('active')); |
|||
this.target.trigger('targetClassUpdated'); |
|||
}, |
|||
|
|||
/** |
|||
* Remove tag from the selected component |
|||
* @param {Object} e |
|||
*/ |
|||
removeTag: function(e){ |
|||
var comp = this.target.get('selectedComponent'); |
|||
|
|||
if(comp) |
|||
comp.get('classes').remove(this.model); |
|||
|
|||
if(this.coll){ |
|||
this.coll.remove(this.model); |
|||
this.target.trigger('targetClassRemoved'); |
|||
} |
|||
|
|||
this.remove(); |
|||
}, |
|||
|
|||
/** |
|||
* Update status of the checkbox |
|||
*/ |
|||
updateStatus: function(){ |
|||
if(!this.$chk) |
|||
this.$chk = this.$el.find('#' + this.pfx + 'checkbox'); |
|||
|
|||
if(this.model.get('active')){ |
|||
this.$chk.removeClass('fa-circle-o').addClass('fa-dot-circle-o'); |
|||
this.$el.removeClass('opac50'); |
|||
}else{ |
|||
this.$chk.removeClass('fa-dot-circle-o').addClass('fa-circle-o'); |
|||
this.$el.addClass('opac50'); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Update label's input |
|||
*/ |
|||
updateInputLabel: function(){ |
|||
if(!this.$labelInput) |
|||
this.$labelInput = this.$el.find('input'); |
|||
var size = this.$labelInput.val().length - 2; |
|||
size = size < 1 ? 1 : size; |
|||
this.$labelInput.attr('size', size); |
|||
}, |
|||
|
|||
/** @inheritdoc */ |
|||
render : function(){ |
|||
this.$el.html( this.template({ |
|||
label: this.model.get('label'), |
|||
pfx: this.pfx, |
|||
ppfx: this.ppfx, |
|||
inputProp: this.inputProp, |
|||
})); |
|||
this.updateStatus(); |
|||
this.$el.attr('class', this.className); |
|||
this.updateInputLabel(); |
|||
return this; |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -1,247 +0,0 @@ |
|||
define(['backbone', 'text!./../template/classTags.html', './ClassTagView'], |
|||
function (Backbone, tagsTemplate, ClassTagView) { |
|||
/** |
|||
* @class ClassTagsView |
|||
* */ |
|||
return Backbone.View.extend({ |
|||
|
|||
template: _.template(tagsTemplate), |
|||
|
|||
events: {}, |
|||
|
|||
initialize: function(o) { |
|||
this.config = o.config || {}; |
|||
this.pfx = this.config.stylePrefix || ''; |
|||
this.className = this.pfx + 'tags'; |
|||
this.addBtnId = this.pfx + 'add-tag'; |
|||
this.newInputId = this.pfx + 'new'; |
|||
this.stateInputId = this.pfx + 'states'; |
|||
this.stateInputC = this.pfx + 'input-c'; |
|||
this.states = this.config.states || []; |
|||
this.events['click #' + this.addBtnId] = 'startNewTag'; |
|||
this.events['blur #' + this.newInputId] = 'endNewTag'; |
|||
this.events['keyup #' + this.newInputId] = 'onInputKeyUp'; |
|||
this.events['change #' + this.stateInputId] = 'stateChanged'; |
|||
|
|||
this.target = this.config.target; |
|||
|
|||
this.listenTo(this.target ,'change:selectedComponent',this.componentChanged); |
|||
this.listenTo(this.target, 'targetClassUpdated', this.updateSelector); |
|||
|
|||
this.listenTo(this.collection, 'add', this.addNew); |
|||
this.listenTo(this.collection, 'reset', this.renderClasses); |
|||
this.listenTo(this.collection, 'remove', this.tagRemoved); |
|||
|
|||
this.delegateEvents(); |
|||
}, |
|||
|
|||
/** |
|||
* Triggered when a tag is removed from collection |
|||
* @param {Object} model Removed model |
|||
*/ |
|||
tagRemoved: function(model){ |
|||
this.updateStateVis(); |
|||
}, |
|||
|
|||
/** |
|||
* Create select input with states |
|||
* @return {string} String of options |
|||
*/ |
|||
getStateOptions: function(){ |
|||
var strInput = ''; |
|||
for(var i = 0; i < this.states.length; i++){ |
|||
strInput += '<option value="' + this.states[i].name + '">' + this.states[i].label + '</option>'; |
|||
} |
|||
return strInput; |
|||
}, |
|||
|
|||
/** |
|||
* Add new model |
|||
* @param {Object} model |
|||
*/ |
|||
addNew: function(model){ |
|||
this.addToClasses(model); |
|||
}, |
|||
|
|||
/** |
|||
* Start tag creation |
|||
* @param {Object} e |
|||
* |
|||
*/ |
|||
startNewTag: function(e) { |
|||
this.$addBtn.hide(); |
|||
this.$input.show().focus(); |
|||
}, |
|||
|
|||
/** |
|||
* End tag creation |
|||
* @param {Object} e |
|||
* |
|||
*/ |
|||
endNewTag: function(e) { |
|||
this.$addBtn.show(); |
|||
this.$input.hide().val(''); |
|||
}, |
|||
|
|||
/** |
|||
* Checks what to do on keyup event |
|||
* @param {Object} e |
|||
*/ |
|||
onInputKeyUp: function(e) { |
|||
if (e.keyCode === 13) |
|||
this.addNewTag(this.$input.val()); |
|||
else if(e.keyCode === 27) |
|||
this.endNewTag(); |
|||
}, |
|||
|
|||
/** |
|||
* Triggered when component is changed |
|||
* @param {Object} e |
|||
*/ |
|||
componentChanged: function(e){ |
|||
this.compTarget = this.target.get('selectedComponent'); |
|||
if(this.compTarget) |
|||
this.$states.val(this.compTarget.get('state')); |
|||
var models = this.compTarget ? this.compTarget.get('classes').models : []; |
|||
this.collection.reset(models); |
|||
this.updateStateVis(); |
|||
}, |
|||
|
|||
/** |
|||
* Update states visibility. Hides states in case there is no tags |
|||
* inside collection |
|||
*/ |
|||
updateStateVis: function(){ |
|||
if(this.collection.length) |
|||
this.$statesC.css('display','block'); |
|||
else |
|||
this.$statesC.css('display','none'); |
|||
this.updateSelector(); |
|||
}, |
|||
|
|||
/** |
|||
* Udpate selector helper |
|||
* @return {this} |
|||
* @private |
|||
*/ |
|||
updateSelector: function(){ |
|||
this.compTarget = this.target.get('selectedComponent'); |
|||
if(!this.compTarget || !this.compTarget.get) |
|||
return; |
|||
var result = ''; |
|||
var models = this.compTarget.get('classes'); |
|||
models.each(function(model){ |
|||
if(model.get('active')) |
|||
result += '.' + model.get('name'); |
|||
}); |
|||
var state = this.compTarget.get('state'); |
|||
result = state ? result + ':' + state : result; |
|||
var el = this.el.querySelector('#' + this.pfx + 'sel'); |
|||
if(el) |
|||
el.innerHTML = result; |
|||
}, |
|||
|
|||
/** |
|||
* Triggered when the select with states is changed |
|||
* @param {Object} e |
|||
*/ |
|||
stateChanged: function(e){ |
|||
if(this.compTarget){ |
|||
this.compTarget.set('state', this.$states.val()); |
|||
if(this.target) |
|||
this.target.trigger('targetStateUpdated'); |
|||
this.updateSelector(); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Add new tag to collection, if possible, and to the component |
|||
* @param {Object} e |
|||
*/ |
|||
addNewTag: function(name){ |
|||
if(!name) |
|||
return; |
|||
|
|||
if(this.target){ |
|||
var cm = this.target.get('ClassManager'); |
|||
var model = cm.addClass(name); |
|||
|
|||
if(this.compTarget){ |
|||
var targetCls = this.compTarget.get('classes'); |
|||
var lenB = targetCls.length; |
|||
targetCls.add(model); |
|||
var lenA = targetCls.length; |
|||
this.collection.add(model); |
|||
|
|||
if(lenA > lenB) |
|||
this.target.trigger('targetClassAdded'); |
|||
|
|||
this.updateStateVis(); |
|||
} |
|||
} |
|||
this.endNewTag(); |
|||
}, |
|||
|
|||
/** |
|||
* Add new object to collection |
|||
* @param {Object} model Model |
|||
* @param {Object} fragmentEl Fragment collection |
|||
* |
|||
* @return {Object} Object created |
|||
* */ |
|||
addToClasses: function(model, fragmentEl) { |
|||
var fragment = fragmentEl || null; |
|||
|
|||
var view = new ClassTagView({ |
|||
model: model, |
|||
config: this.config, |
|||
coll: this.collection, |
|||
}); |
|||
var rendered = view.render().el; |
|||
|
|||
if(fragment) |
|||
fragment.appendChild( rendered ); |
|||
else |
|||
this.$classes.append(rendered); |
|||
|
|||
return rendered; |
|||
}, |
|||
|
|||
/** |
|||
* Render the collection of classes |
|||
* @return {this} |
|||
*/ |
|||
renderClasses: function() { |
|||
var fragment = document.createDocumentFragment(); |
|||
|
|||
this.collection.each(function(model){ |
|||
this.addToClasses(model, fragment); |
|||
},this); |
|||
|
|||
if(this.$classes) |
|||
this.$classes.empty().append(fragment); |
|||
|
|||
return this; |
|||
}, |
|||
|
|||
|
|||
/** @inheritdoc */ |
|||
render : function(){ |
|||
this.$el.html( this.template({ |
|||
label: this.config.label, |
|||
statesLabel: this.config.statesLabel, |
|||
pfx: this.pfx, |
|||
})); |
|||
this.$input = this.$el.find('input#' + this.newInputId); |
|||
this.$addBtn = this.$el.find('#' + this.addBtnId); |
|||
this.$classes = this.$el.find('#' + this.pfx + 'tags-c'); |
|||
this.$states = this.$el.find('#' + this.stateInputId); |
|||
this.$statesC = this.$el.find('#' + this.stateInputC); |
|||
this.$states.append(this.getStateOptions()); |
|||
this.renderClasses(); |
|||
this.$el.attr('class', this.className); |
|||
return this; |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue