Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ampersand-view

Package Overview
Dependencies
Maintainers
2
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ampersand-view - npm Package Compare versions

Comparing version 4.2.3 to 5.0.0

236

ampersand-view.js

@@ -6,4 +6,5 @@ var State = require('ampersand-state');

var events = require('events-mixin');
var classes = require('component-classes');
var matches = require('matches-selector');
var bindings = require('dom-bindings');
var getPath = require('get-object-path');

@@ -17,3 +18,3 @@

this.on('change:el', this.handleElementChange, this);
this._parsedBindings = {};
this._parsedBindings = bindings(this.bindings);
this._initializeBindings();

@@ -40,11 +41,11 @@ this.initialize.apply(this, arguments);

},
model: {
collection: {
set: function (newVal) {
return {
val: newVal,
type: (newVal && newVal.initialize) ? 'model' : typeof newVal
type: newVal && newVal.isCollection ? 'collection' : typeof newVal
};
},
compare: function (m1, m2) {
return m1 === m2;
compare: function (currentVal, newVal) {
return currentVal === newVal;
}

@@ -54,5 +55,5 @@ }

props: {
model: 'model',
model: 'state',
el: 'element',
collection: 'model'
collection: 'collection'
},

@@ -117,6 +118,3 @@ derived: {

render: function () {
this.renderWithTemplate({
model: this.model,
collection: this.collection
});
this.renderWithTemplate(this);
return this;

@@ -151,3 +149,3 @@ },

this.delegateEvents();
this._applyAllBindings();
this._applyBindingsForKey();
return this;

@@ -215,168 +213,11 @@ },

_applyAllBindings: function () {
_applyBindingsForKey: function (name) {
if (!this.el) return;
var self = this;
_.each(this._parsedBindings, function (value, key) {
self._applyBindingsForModel(key);
});
},
_applyBindingsForModel: function (name) {
if (!this.el) return;
var self = this;
var model = this[name];
var bindings, fns;
if (!model) return;
bindings = this._parsedBindings[name];
if (!bindings) return;
_.each(bindings, function (fns, key) {
_.each(fns, function (fn) {
fn();
});
});
},
_applyBindingsForModelProperty: function (modelName, prop) {
if (!this.el) return;
var bindings = this._parsedBindings[modelName];
var fns = bindings && bindings[prop];
if (!fns) return;
_.each(fns, function (fn) {
fn();
});
},
// Builds array of functions grouped by model name and property
// that can be used to apply a binding rule based on model name
// and optionally property.
// The result of this parsing looks something like this:
//
// _parsedBindings: {
// model: {
// active: [
// fn1,
// fn2,
// ..etc..
// ]
// }
// }
//
// Then, we can use these as callbacks for change handlers or simply
// or call them all if the root element is replaced
_parseBindings: function (modelName, bindings) {
var self = this;
var processedBindings = {};
var bindingsForModel = this._parsedBindings[modelName] || (this._parsedBindings[modelName] = {});
// create new object with same keys but
// with arrays for values.
// This is where we'll push our results
_.each(_.keys(bindings), function (key) {
processedBindings[key] = [];
});
function addBinding(propName, definition) {
var processedDef = processedBindings[propName];
var selector, attr, name, split;
if (typeof definition === 'string') {
processedDef.push([definition, 'text']);
} else {
selector = definition[0];
attr = definition[1];
name = definition[2];
split = attr.split(' ');
if (split.length > 1) {
_.each(split, function (attributeName) {
processedDef.push([selector, attributeName, name]);
});
} else {
processedDef.push([selector, attr, name]);
}
}
var fns = this._parsedBindings.getGrouped(name);
var item;
for (item in fns) {
fns[item].forEach(function (fn) {
fn(this.el, getPath(this, item), _.last(item.split('.')));
}, this);
}
// extract any nested bindings
_.each(bindings, function (value, propertyName) {
if (_.isArray(value) && _.isArray(value[0])) {
_.each(value, function (item) {
addBinding(propertyName, item);
});
} else {
addBinding(propertyName, value);
}
});
_.each(processedBindings, function (value, propertyName) {
_.each(value, function (value) {
var selector = value[0];
var attr = value[1];
var attributeName = value[2];
var fns = bindingsForModel[propertyName] || (bindingsForModel[propertyName] = []);
// handle text bindings
if (attr === 'text') {
fns.push(function () {
_.each(self.getAll(selector), function (el) {
var model = self[modelName];
var value = model && model.get(propertyName);
el.textContent = value || '';
});
});
return;
}
if (attr === 'html') {
fns.push(function () {
_.each(self.getAll(selector), function (el) {
var model = self[modelName];
var value = model && model.get(propertyName);
el.innerHTML = value || '';
});
});
return;
}
if (attr === 'class') {
fns.push(function () {
_.each(self.getAll(selector), function (el) {
var model = self[modelName];
var newVal = model && model.get(propertyName);
var classList = classes(el);
var prevVal;
if (_.isBoolean(newVal)) {
classList.toggle(propertyName, newVal);
} else {
prevVal = model.previous(propertyName);
if (prevVal) classList.remove(prevVal);
if (newVal) classList.add(newVal);
}
});
});
return;
}
// treat 'classList' attrs like
// set/get for class attr
if (attr === 'classList') {
attr = 'class';
}
fns.push(function () {
_.each(self.getAll(selector), function (el) {
var model = self[modelName];
var newVal = model && model.get(propertyName);
// coerce undefined to empty string
if (_.isUndefined(newVal)) newVal = '';
// now we can treat them all the same
if (_.isBoolean(newVal) && !newVal) {
el.removeAttribute(attributeName);
} else {
el.setAttribute(attr, attributeName || newVal);
}
});
});
});
});
},

@@ -387,35 +228,7 @@

if (!this.bindings) return;
var self = this;
var firstVal;
function register(name) {
var model = self[name];
var modelChangeHandler = function (view, newModel) {
var oldModel = self.previous(name);
if (oldModel) self.stopListening(oldModel);
// apply previuos bindings with new model present
self._applyBindingsForModel(name);
_.each(self._parsedBindings[name], function (thing, key) {
self.listenTo(newModel, 'change:' + key, function (model, newVal) {
self._applyBindingsForModelProperty(name, key);
});
});
};
//
self.on('change:' + name, modelChangeHandler);
if (model) modelChangeHandler(self, model);
}
firstVal = _.values(this.bindings)[0];
// we can assume we're only binding on `model` here.
if (_.isArray(firstVal) || _.isString(firstVal)) {
self._parseBindings('model', this.bindings);
register('model');
} else {
_.each(this.bindings, function (value, key) {
self._parseBindings(key, value);
register(key);
});
}
this.on('all', function (eventName) {
if (eventName.slice(0, 7) === 'change:') {
this._applyBindingsForKey(eventName.split(':')[1]);
}
}, this);
},

@@ -441,4 +254,3 @@

if (!template) throw new Error('Template string or function needed.');
var newDom = _.isString(template) ? template : template(context || {model: this.model});
if (_.isString(newDom)) newDom = domify(newDom);
var newDom = domify(_.isString(template) ? template : template(context || this));
var parent = this.el && this.el.parentNode;

@@ -445,0 +257,0 @@ if (parent) parent.replaceChild(newDom, this.el);

11

package.json
{
"name": "ampersand-view",
"description": "A smart base view for Backbone apps, to make it easy to bind collections and properties to the DOM.",
"version": "4.2.3",
"version": "5.0.0",
"author": "Henrik Joreteg <henrik@andyet.net>",

@@ -9,7 +9,10 @@ "browser": "./ampersand-view.js",

"dependencies": {
"ampersand-state": "^4.0.0",
"ampersand-state": "~4.2.1",
"backbone-events-standalone": "0.x.x",
"component-classes": "1.x.x",
"dom-bindings": "~0.2.0",
"domify": "1.x.x",
"events-mixin": "^1.1.0",
"get-object-path": "0.0.2",
"key-tree-store": "~0.3.0",
"matches-selector": "1.x.x",

@@ -19,4 +22,4 @@ "underscore": "1.x.x"

"devDependencies": {
"ampersand-model": "~2.7.2",
"ampersand-rest-collection": "~0.1.6",
"ampersand-model": "~2.10.6",
"ampersand-rest-collection": "^1.0.3",
"browserify": "~3.41.0",

@@ -23,0 +26,0 @@ "precommit-hook": "0.x.x",

@@ -43,2 +43,7 @@ # ampersand-view

Full description of the available binding types can be found here: https://github.com/henrikjoreteg/dom-bindings#binding-types
A few examples below:
```javascript

@@ -56,40 +61,52 @@ var MyView = AmpersandView.extend({

// if it's changed.
name: '.userName',
'model.name': '.userName',
// If the `active` property is a boolean the class
// will be added/removed based on the boolean property.
// But, if the `active` property were a string, the previous
// value would be removed and the new one added from the class
// list without affecting other classes that may alreay be there.
active: ['li', 'class'],
// You can add/remove a class based on a boolean property.
// Based on the truthiness of the `active` property an
// "active" class will be added or removed from element(s)
// that match the selector. without affecting other classes
// that may alreay be there.
'model.active': {
type: 'booleanClass',
selector: 'li',
// you can optionally specify a name like this
// name: 'is-active'
// or even a true/false case like this:
// yes: 'is-active',
// no: 'is-not-active'
// if no name is given it will use the property name
// by default.
},
// If you've got a boolean property, you can also specify a third
// item in the array. It will be used to determine what the class
// is that will be toggled. This way your property name can be
// different than the class.
isActive: ['li', 'class', 'active'], // will toggle the 'active' class
// If you prefer, you *can* also bind to the whole class list. Which
// will wipe out all existing classes.
myClasses: ['li', 'classList'],
// As you might have guessed, you can bind to any attribute you want
userUrl: ['a', 'href'],
'model.userUrl': {
type: 'attribute',
name: 'href',
selector: 'a'
},
// This works for boolean attributes. The following would add and remove
// the entire `checked` attribute (assuming the property value was a boolean)
selected: ['input', 'checked']
selected: {
type: 'booleanAttribute',
selector: 'input',
name: 'checked'
},
// If you really need to, you can even bind the same attribute to different
// types of things with different options. If "superActive" was a string, the following would put
// the text value of it, inside `.userName` and add it as a class on the `li`.
// If you really need to, you can have as many bindings as you want for the same
// property, just pass an array of binding definitions:
superActive: [
// the *only* restriction here is that if you pass an array of binding
// declarations for a single property, each sub-item must also be an
// array.
['.userName'],
['li', 'class'],
// you can even get crazy... this would bind both
// data attributes to both the li and .username elements
['li, .username', 'data-attribute1 data-attribute2'],
{
type: 'text',
selector: '.username'
},
{
type: 'class',
selector: 'li'
},
// btw, also works for selectors that match multiple elements
{
type: 'booleanAttribute',
selector: '.username, .thing'
}
]

@@ -96,0 +113,0 @@ },

@@ -42,2 +42,3 @@ var test = require('tape');

test('registerSubview', function (t) {

@@ -104,3 +105,3 @@ var removeCalled = 0;

bindings: {
name: 'span'
'model.name': 'span'
}

@@ -117,3 +118,6 @@ });

bindings: {
name: ['', 'html']
'model.name': {
type: 'innerHTML',
selector: ''
}
}

@@ -130,3 +134,7 @@ });

bindings: {
url: ['img', 'src']
'model.url': {
type: 'attribute',
name: 'src',
selector: 'img'
}
}

@@ -145,3 +153,7 @@ });

bindings: {
url: ['', 'href']
'model.url': {
type: 'attribute',
name: 'href',
selector: ''
}
}

@@ -160,3 +172,7 @@ });

bindings: {
something: ['input', 'value']
'model.something': {
type: 'attribute',
selector: 'input',
name: 'value'
}
}

@@ -180,4 +196,8 @@ });

bindings: {
fireDanger: ['', 'class'],
active: ['', 'class']
'model.fireDanger': {
type: 'class'
},
'model.active': {
type: 'booleanClass'
}
}

@@ -199,26 +219,6 @@ }, model);

test('classList bindings', function (t) {
var model = new Model();
model.set({
fireDanger: 'high',
active: true,
something: 'cool'
});
var view = getView({
autoRender: true,
template: '<li class="something else perhaps"></li>',
bindings: {
classes: ['', 'classList']
}
}, model);
t.equal(view.el.className, 'cooltrue', 'wipes out existing classes');
t.end();
});
test('nested binding definitions', function (t) {
var model = new Model();
model.set({
fireDanger: 'high',
active: true,
something: 'cool'
active: true
});

@@ -229,7 +229,19 @@ var View = AmpersandView.extend({

bindings: {
active: [
['div', 'data-active data-something'],
['div', 'text'],
['', 'class'],
['div', 'classList', 'selected']
'model.active': [
{
type: 'booleanAttribute',
name: 'data-active',
selector: 'div'
},
{
type: 'booleanAttribute',
name: 'data-something',
selector: 'div'
},
{
selector: 'div'
},
{
type: 'booleanClass'
}
]

@@ -245,3 +257,2 @@ }

t.ok(contains(li.className, 'active'));
t.equal(div.className, 'selected');
t.end();

@@ -335,33 +346,32 @@ });

/*
test('focus/blur events should work in events hash. Issue #8', function (t) {
t.plan(2);
var View = AmpersandView.extend({
events: {
'focus #thing': 'handleFocus',
'blur #thing': 'handleBlur'
},
autoRender: true,
template: '<div><input id="thing"/></div></div>',
handleFocus: function () {
t.pass('focus called');
},
handleBlur: function () {
t.pass('blur called');
t.end();
}
});
var view = new View();
// should be able to do this without
// ending up with too many handlers
view.delegateEvents();
view.delegateEvents();
view.delegateEvents();
document.body.appendChild(view.el);
view.el.firstChild.focus();
view.el.firstChild.blur();
document.body.removeChild(view.el);
});
*/
//test('focus/blur events should work in events hash. Issue #8', function (t) {
// t.plan(2);
// var View = AmpersandView.extend({
// events: {
// 'focus #thing': 'handleFocus',
// 'blur #thing': 'handleBlur'
// },
// autoRender: true,
// template: '<div><input id="thing"/></div></div>',
// handleFocus: function () {
// t.pass('focus called');
// },
// handleBlur: function () {
// t.pass('blur called');
// t.end();
// }
// });
// var view = new View();
// // should be able to do this without
// // ending up with too many handlers
// view.delegateEvents();
// view.delegateEvents();
// view.delegateEvents();
//
// document.body.appendChild(view.el);
// view.el.firstChild.focus();
// view.el.firstChild.blur();
// document.body.removeChild(view.el);
//});

@@ -458,7 +468,6 @@ test('ability to mix in state properties', function (t) {

bindings: {
model1: {
name: '#model1'
},
model2: {
name: ['#model2', 'class']
'model1.name': '#model1',
'model2.name': {
type: 'class',
selector: '#model2'
}

@@ -482,3 +491,3 @@ }

bindings: {
name: ''
'model.name': ''
}

@@ -501,3 +510,3 @@ });

bindings: {
name: ''
'model.name': ''
}

@@ -525,3 +534,3 @@ });

bindings: {
name: ''
'model.name': ''
}

@@ -528,0 +537,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc