mirror of https://github.com/artf/grapesjs.git
31 changed files with 765 additions and 226 deletions
@ -1,3 +1,3 @@ |
|||
<span id="<%= pfx %>checkbox" class="fa"></span> |
|||
<span id="<%= pfx %>tag-label"> <%= label %> </span> |
|||
<span id="<%= pfx %>tag-label"><%= label %></span> |
|||
<span id="<%= pfx %>close">⨯</span> |
|||
@ -0,0 +1,11 @@ |
|||
define(function () { |
|||
return { |
|||
|
|||
// Style prefix
|
|||
stylePrefix: 'css-', |
|||
|
|||
// Default CSS style
|
|||
'default': '', |
|||
|
|||
}; |
|||
}); |
|||
@ -0,0 +1,134 @@ |
|||
define(function(require) { |
|||
/** |
|||
* @class CssComposer |
|||
* @param {Object} config Configurations |
|||
* |
|||
* */ |
|||
var CssComposer = function(config) |
|||
{ |
|||
var c = config || {}, |
|||
def = require('./config/config'), |
|||
CssRule = require('./model/CssRule'), |
|||
CssRules = require('./model/CssRules'), |
|||
Selectors = require('./model/Selectors'), |
|||
CssRulesView = require('./view/CssRulesView'); |
|||
|
|||
for (var name in def) { |
|||
if (!(name in c)) |
|||
c[name] = def[name]; |
|||
} |
|||
|
|||
//this.qset = { '' : CssRules, '340px': CssRules };
|
|||
var rules = new CssRules([]), |
|||
rulesView = new CssRulesView({ |
|||
collection: rules, |
|||
config: c, |
|||
}); |
|||
|
|||
return { |
|||
|
|||
Selectors: Selectors, |
|||
|
|||
/** |
|||
* Create new rule and return it. Don't add it to the collection |
|||
* @param {Array} selectors Array of selectors |
|||
* @param {String} state Css rule state |
|||
* @param {String} width For which device this style is oriented |
|||
* |
|||
* @return {Object} |
|||
* */ |
|||
newRule: function(selectors, state, width) { |
|||
var s = state || ''; |
|||
var w = width || ''; |
|||
var rule = new CssRule({ |
|||
state: s, |
|||
maxWidth: w, |
|||
}); |
|||
rule.get('selectors').add(selectors); |
|||
return rule; |
|||
}, |
|||
|
|||
/** |
|||
* Add new rule to the collection if not yet exists |
|||
* @param {Object} rule |
|||
* |
|||
* @return {Object} |
|||
* */ |
|||
addRule: function(rule){ |
|||
var models = rule.get('selectors').models; |
|||
var r = this.getRule(models, rule.get('state'), rule.get('maxWidth')); |
|||
if(!r) |
|||
r = rules.add(rule); |
|||
return r; |
|||
}, |
|||
|
|||
/** |
|||
* Get class by its name |
|||
* @param {Array} selectors Array of selectors |
|||
* @param {String} state Rule status |
|||
* @param {String} set Query set |
|||
* |
|||
* @return {Object|null} |
|||
* */ |
|||
getRule : function(selectors, state, set) { |
|||
var req = _.pluck(selectors, 'cid'); |
|||
fRule = null; |
|||
rules.each(function(rule){ |
|||
if(fRule) |
|||
return; |
|||
var sel = _.pluck(rule.get('selectors').models, 'cid'); |
|||
if(this.same(req, sel)) |
|||
fRule = rule; |
|||
}, this); |
|||
return fRule; |
|||
}, |
|||
|
|||
/** |
|||
* Compare 2 arrays to check if are the same |
|||
* @param {Array} arr1 |
|||
* @param {Array} arr2 |
|||
* |
|||
* @return {Boolean} |
|||
*/ |
|||
same: function(a1, a2){ |
|||
if(a1.length !== a2.length) |
|||
return; |
|||
|
|||
for (var i = 0; i < a1.length; i++) { |
|||
var f = 0; |
|||
|
|||
for (var j = 0; j < a2.length; j++) { |
|||
if (a1[i] === a2[j]) |
|||
f = 1; |
|||
} |
|||
|
|||
if(f === 0) |
|||
return; |
|||
} |
|||
return true; |
|||
}, |
|||
|
|||
/** |
|||
* Get collection of css rules |
|||
* |
|||
* @return {Object} |
|||
* */ |
|||
getRules : function() { |
|||
return rules; |
|||
}, |
|||
|
|||
/** |
|||
* Render block of CSS rules |
|||
* |
|||
* @return {Object} |
|||
*/ |
|||
render: function(){ |
|||
return rulesView.render().el; |
|||
} |
|||
|
|||
}; |
|||
}; |
|||
|
|||
return CssComposer; |
|||
|
|||
}); |
|||
@ -0,0 +1,28 @@ |
|||
define(['backbone', './Selectors'], |
|||
function (Backbone, Selectors) { |
|||
/** |
|||
* @class CssRule |
|||
* */ |
|||
return Backbone.Model.extend({ |
|||
|
|||
defaults: { |
|||
// Css selectors
|
|||
selectors: {}, |
|||
// Css properties style
|
|||
style: {}, |
|||
// On which device width this rule should be rendered, eg. @media (max-width: 1000px)
|
|||
maxWidth: '', |
|||
// State of the rule, eg: hover | pressed | focused
|
|||
state: '', |
|||
// Indicates if the rule is stylable
|
|||
stylable: true, |
|||
}, |
|||
|
|||
initialize: function(c, opt) { |
|||
this.config = c || {}; |
|||
this.slct = this.config.selectors || []; |
|||
this.set('selectors', new Selectors(this.slct)); |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,29 @@ |
|||
define([ 'backbone', 'require'], |
|||
function (Backbone, require) { |
|||
/** |
|||
* @class Selectors |
|||
* */ |
|||
|
|||
return Backbone.Collection.extend({ |
|||
|
|||
initialize: function(models, opt){ |
|||
|
|||
this.model = function(attrs, opts) { |
|||
var model; |
|||
|
|||
switch(1){ |
|||
|
|||
default: |
|||
if(!this.ClassTag) |
|||
this.ClassTag = require("ClassManager/model/ClassTag"); |
|||
model = new this.ClassTag(attrs, opts); |
|||
|
|||
} |
|||
|
|||
return model; |
|||
}; |
|||
|
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,53 @@ |
|||
define(['backbone'], |
|||
function (Backbone) { |
|||
/** |
|||
* @class CssRuleView |
|||
* */ |
|||
return Backbone.View.extend({ |
|||
|
|||
tagName: 'style', |
|||
|
|||
initialize: function(o) { |
|||
this.config = o.config || {}; |
|||
this.listenTo(this.model, 'change:style', this.render); |
|||
|
|||
}, |
|||
|
|||
/** |
|||
* Returns string of selectors |
|||
* @return {String} |
|||
*/ |
|||
renderSelectors: function(){ |
|||
var sel = []; |
|||
this.model.get('selectors').each(function(m){ |
|||
sel.push('.' + m.get('name')); |
|||
}); |
|||
return sel.join(''); |
|||
}, |
|||
|
|||
/** |
|||
* Returns string of properties |
|||
* @return {String} |
|||
*/ |
|||
renderProperties: function(){ |
|||
var sel = [], |
|||
props = this.model.get('style'); |
|||
for (var prop in props){ |
|||
sel.push(prop + ':' + props[prop] + ';'); |
|||
} |
|||
return sel.join(''); |
|||
}, |
|||
|
|||
/* |
|||
http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript
|
|||
*/ |
|||
render : function(){ |
|||
if(!this.selStr) |
|||
this.selStr = this.renderSelectors(); |
|||
var prpStr = this.renderProperties(); |
|||
this.$el.html(this.selStr + '{' + prpStr + '}'); |
|||
return this; |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,62 @@ |
|||
define(['backbone','./CssRuleView'], |
|||
function (Backbone, CssRuleView) { |
|||
/** |
|||
* @class CssRulesView |
|||
* */ |
|||
return Backbone.View.extend({ |
|||
|
|||
initialize: function(o) { |
|||
this.config = o.config; |
|||
this.preview = o.preview; |
|||
this.pfx = this.config.stylePrefix; |
|||
this.listenTo( this.collection, 'add', this.addTo ); |
|||
this.listenTo( this.collection, 'reset', this.render ); |
|||
}, |
|||
|
|||
/** |
|||
* Add to collection |
|||
* @param {Object} model |
|||
* */ |
|||
addTo: function(model){ |
|||
//console.log('Added');
|
|||
this.addToCollection(model); |
|||
}, |
|||
|
|||
/** |
|||
* Add new object to collection |
|||
* @param {Object} model |
|||
* @param {Object} fragmentEl |
|||
* |
|||
* @return {Object} |
|||
* */ |
|||
addToCollection: function(model, fragmentEl){ |
|||
var fragment = fragmentEl || null; |
|||
var viewObject = CssRuleView; |
|||
|
|||
var view = new viewObject({ |
|||
model: model, |
|||
config: this.config, |
|||
}); |
|||
var rendered = view.render().el; |
|||
|
|||
if(fragment) |
|||
fragment.appendChild( rendered ); |
|||
else |
|||
this.$el.append(rendered); |
|||
|
|||
return rendered; |
|||
}, |
|||
|
|||
render: function() { |
|||
var fragment = document.createDocumentFragment(); |
|||
this.$el.empty(); |
|||
|
|||
this.collection.each(function(model){ |
|||
this.addToCollection(model, fragment); |
|||
}, this); |
|||
|
|||
this.$el.append(fragment); |
|||
return this; |
|||
} |
|||
}); |
|||
}); |
|||
@ -1,8 +0,0 @@ |
|||
define(function () { |
|||
return { |
|||
|
|||
// Style prefix
|
|||
stylePrefix : 'css-', |
|||
|
|||
}; |
|||
}); |
|||
@ -1,64 +0,0 @@ |
|||
define(function(require) { |
|||
/** |
|||
* @class CssManager |
|||
* @param {Object} config Configurations |
|||
* |
|||
* */ |
|||
var CssManager = function(config) |
|||
{ |
|||
var c = config || {}, |
|||
def = require('./config/config'); |
|||
this.CssRules = require('./model/CssRules'); |
|||
|
|||
for (var name in def) { |
|||
if (!(name in c)) |
|||
c[name] = def[name]; |
|||
} |
|||
|
|||
this.rules = new this.CssRules([]); |
|||
this.config = c; |
|||
}; |
|||
|
|||
CssManager.prototype = { |
|||
|
|||
/** |
|||
* Add new class to collection only if it's not already exists |
|||
* @param {String} name Class name |
|||
* |
|||
* @return {Object} Model class |
|||
* */ |
|||
addRule: function(name){ |
|||
var label = name; |
|||
var c = this.getClass(name); |
|||
if(!c) |
|||
return this.classes.add({name: name, label: label}); |
|||
return c; |
|||
}, |
|||
|
|||
/** |
|||
* Get class by its name |
|||
* @param {Array[String]} ids Array of ids |
|||
* @param {String} status Rule status |
|||
* @param {String} set Query set |
|||
* |
|||
* @return {Object|null} |
|||
* */ |
|||
getRule : function(ids, status, set) { |
|||
var res = this.classes.where({name: id}); |
|||
return res.length ? res[0] : null; |
|||
}, |
|||
|
|||
/** |
|||
* Get collection of css rules |
|||
* |
|||
* @return {Object} |
|||
* */ |
|||
getRules : function() { |
|||
return this.rules; |
|||
}, |
|||
|
|||
}; |
|||
|
|||
return ClassManager; |
|||
|
|||
}); |
|||
@ -1,16 +0,0 @@ |
|||
define(['backbone'], |
|||
function (Backbone) { |
|||
/** |
|||
* @class CssRule |
|||
* */ |
|||
return Backbone.Model.extend({ |
|||
|
|||
defaults: { |
|||
classes: {}, |
|||
style: {}, |
|||
stylable: true, |
|||
state: '', |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,143 @@ |
|||
var path = 'ClassManager/view/'; |
|||
define([path + 'ClassTagsView', 'ClassManager/model/ClassTags'], |
|||
function(ClassTagsView, ClassTags) { |
|||
|
|||
return { |
|||
run : function(){ |
|||
describe('ClassTagsView', function() { |
|||
|
|||
before(function () { |
|||
this.$fixtures = $("#fixtures"); |
|||
this.$fixture = $('<div class="classtag-fixture"></div>'); |
|||
}); |
|||
|
|||
beforeEach(function () { |
|||
this.target = { get: function(){} }; |
|||
this.coll = new ClassTags(); |
|||
_.extend(this.target, Backbone.Events); |
|||
|
|||
this.view = new ClassTagsView({ |
|||
config : { target: this.target }, |
|||
collection: this.coll |
|||
}); |
|||
|
|||
this.$fixture.empty().appendTo(this.$fixtures); |
|||
this.$fixture.html(this.view.render().el); |
|||
this.btnAdd = this.view.$el.find('#' + this.view.addBtnId); |
|||
this.input = this.view.$el.find('input#' + this.view.newInputId); |
|||
this.$tags = this.$fixture.find('#tags-c'); |
|||
}); |
|||
|
|||
afterEach(function () { |
|||
delete this.view.collection; |
|||
}); |
|||
|
|||
after(function () { |
|||
this.$fixture.remove(); |
|||
}); |
|||
|
|||
it('Object exists', function() { |
|||
ClassTagsView.should.be.exist; |
|||
}); |
|||
|
|||
it('Not tags inside', function() { |
|||
this.$tags.html().should.equal(''); |
|||
}); |
|||
|
|||
it('Add new tag triggers correct method', function() { |
|||
sinon.stub(this.view, "addToClasses"); |
|||
this.coll.add({ name: 'test' }); |
|||
this.view.addToClasses.calledOnce.should.equal(true); |
|||
}); |
|||
|
|||
it('Start new tag creation', function() { |
|||
this.btnAdd.click(); |
|||
(this.btnAdd.css('display') == 'none').should.equal(true); |
|||
(this.input.css('display') !== 'none').should.equal(true); |
|||
}); |
|||
|
|||
it('Stop tag creation', function() { |
|||
this.btnAdd.click(); |
|||
this.input.val('test') |
|||
this.input.blur(); |
|||
(this.btnAdd.css('display') !== 'none').should.equal(true); |
|||
(this.input.css('display') == 'none').should.equal(true); |
|||
this.input.val().should.equal(''); |
|||
}); |
|||
|
|||
it('Check keyup of ESC on input', function() { |
|||
this.btnAdd.click(); |
|||
sinon.stub(this.view, "addNewTag"); |
|||
this.input.trigger({ |
|||
type: 'keyup', |
|||
keyCode: 13 |
|||
}); |
|||
this.view.addNewTag.calledOnce.should.equal(true); |
|||
}); |
|||
|
|||
it('Check keyup on ENTER on input', function() { |
|||
this.btnAdd.click(); |
|||
sinon.stub(this.view, "endNewTag"); |
|||
this.input.trigger({ |
|||
type: 'keyup', |
|||
keyCode: 27 |
|||
}); |
|||
this.view.endNewTag.calledOnce.should.equal(true); |
|||
}); |
|||
|
|||
it('Collection changes on update of target', function() { |
|||
this.coll.add({ name: 'test' }); |
|||
this.target.trigger('change:selectedComponent'); |
|||
this.coll.length.should.equal(0); |
|||
}); |
|||
|
|||
it('Collection reacts on reset', function() { |
|||
this.coll.add([{ name: 'test1' }, { name: 'test2' }]); |
|||
sinon.stub(this.view, "addToClasses"); |
|||
this.coll.trigger('reset'); |
|||
this.view.addToClasses.calledTwice.should.equal(true); |
|||
}); |
|||
|
|||
it("Don't accept empty tags", function() { |
|||
this.view.addNewTag(''); |
|||
this.$tags.html().should.equal(''); |
|||
}); |
|||
|
|||
it("Accept new tags", function() { |
|||
sinon.stub(this.target, "get").returns({ |
|||
addClass: function(v){ |
|||
return {name: v}; |
|||
} |
|||
}); |
|||
this.view.compTarget = { |
|||
get: function(){ |
|||
return { add: function(){} }; |
|||
} |
|||
}; |
|||
this.view.addNewTag('test'); |
|||
this.view.addNewTag('test2'); |
|||
this.$tags.children().length.should.equal(2); |
|||
}); |
|||
|
|||
it("New tag correctly added", function() { |
|||
this.coll.add({ label: 'test' }); |
|||
this.$tags.children().first().find('#tag-label').html().should.equal('test'); |
|||
}); |
|||
|
|||
describe('Should be rendered correctly', function() { |
|||
it('Has label', function() { |
|||
this.view.$el.find('#label').should.have.property(0); |
|||
}); |
|||
it('Has tags container', function() { |
|||
this.view.$el.find('#tags-c').should.have.property(0); |
|||
}); |
|||
it('Has add button', function() { |
|||
this.view.$el.find('#add-tag').should.have.property(0); |
|||
}); |
|||
}); |
|||
|
|||
}); |
|||
} |
|||
}; |
|||
|
|||
}); |
|||
@ -0,0 +1,84 @@ |
|||
var modulePath = './../../../test/specs/css_composer'; |
|||
|
|||
define([ |
|||
'CssComposer', |
|||
modulePath + '/model/CssModels' |
|||
], |
|||
function( |
|||
CssComposer, |
|||
Models, |
|||
Selectors |
|||
) { |
|||
|
|||
describe('Css Composer', function() { |
|||
|
|||
describe('Main', function() { |
|||
|
|||
beforeEach(function () { |
|||
this.obj = new CssComposer(); |
|||
}); |
|||
|
|||
afterEach(function () { |
|||
delete this.obj; |
|||
}); |
|||
|
|||
it('Object exists', function() { |
|||
CssComposer.should.be.exist; |
|||
}); |
|||
|
|||
it("Rules are empty", function() { |
|||
this.obj.getRules().length.should.equal(0); |
|||
}); |
|||
|
|||
it('Create new rule with correct selectors', function() { |
|||
var sel = new this.obj.Selectors(); |
|||
var s1 = sel.add({name: 'test1'}); |
|||
var rule = this.obj.newRule(sel.models); |
|||
rule.get('selectors').at(0).should.deep.equal(s1); |
|||
}); |
|||
|
|||
it('Create new rule correctly', function() { |
|||
var sel = new this.obj.Selectors(); |
|||
var s1 = sel.add({name: 'test1'}); |
|||
var rule = this.obj.newRule(sel.models, 'state1', 'width1'); |
|||
rule.get('state').should.equal('state1'); |
|||
rule.get('maxWidth').should.equal('width1'); |
|||
}); |
|||
|
|||
it("Add rule to collection", function() { |
|||
var sel = new this.obj.Selectors([{name: 'test1'}]); |
|||
var rule = this.obj.newRule(sel.models, 'state1', 'width1'); |
|||
this.obj.addRule(rule); |
|||
this.obj.getRules().length.should.equal(1); |
|||
this.obj.getRules().at(0).get('selectors').at(0).get('name').should.equal('test1'); |
|||
}); |
|||
|
|||
/* |
|||
it("Don't duplicate rules", function() { |
|||
var sel1 = new this.obj.Selectors([{name: 'test1'}]); |
|||
var rule1 = this.obj.newRule(sel1.models, 'state1', 'width1'); |
|||
var aRule1 = this.obj.addRule(rule1); |
|||
|
|||
var sel2 = new this.obj.Selectors([{name: 'test1'}]); |
|||
var rule2 = this.obj.newRule(sel2.models, 'state1', 'width1'); |
|||
var aRule2 = this.obj.addRule(rule2); |
|||
|
|||
console.log(sel1); |
|||
console.log(rule1); |
|||
console.log(aRule1); |
|||
console.log(sel2); |
|||
console.log(rule2); |
|||
console.log(aRule2); |
|||
console.log(this.obj.getRules().length); |
|||
|
|||
aRule1.should.deep.equal(aRule2); |
|||
|
|||
}); |
|||
*/ |
|||
|
|||
}); |
|||
|
|||
Models.run(); |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,60 @@ |
|||
var path = 'CssComposer/model/'; |
|||
define([path + 'CssRule', |
|||
path + 'CssRules', |
|||
path + 'Selectors', |
|||
'ClassManager/model/ClassTag'], |
|||
function(CssRule, CssRules, Selectors, ClassTag) { |
|||
|
|||
return { |
|||
run : function(){ |
|||
describe('CssRule', function() { |
|||
|
|||
beforeEach(function () { |
|||
this.obj = new CssRule(); |
|||
}); |
|||
|
|||
afterEach(function () { |
|||
delete this.obj; |
|||
}); |
|||
|
|||
it('Has selectors property', function() { |
|||
this.obj.has('selectors').should.equal(true); |
|||
}); |
|||
|
|||
it('Has style property', function() { |
|||
this.obj.has('style').should.equal(true); |
|||
}); |
|||
|
|||
it('Has state property', function() { |
|||
this.obj.has('state').should.equal(true); |
|||
}); |
|||
|
|||
it('No default selectors', function() { |
|||
this.obj.get('selectors').length.should.equal(0); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('CssRules', function() { |
|||
|
|||
it('Creates collection item correctly', function() { |
|||
var c = new CssRules(); |
|||
var m = c.add({}); |
|||
m.should.be.an.instanceOf(CssRule); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('Selectors', function() { |
|||
|
|||
it('Creates collection item correctly', function() { |
|||
var c = new Selectors(); |
|||
var m = c.add({}); |
|||
m.should.be.an.instanceOf(ClassTag); |
|||
}); |
|||
|
|||
}); |
|||
} |
|||
}; |
|||
|
|||
}); |
|||
Loading…
Reference in new issue