Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
ampersand-view
Advanced tools
A smart base view for Backbone apps, to make it easy to bind collections and properties to the DOM.
A set of common helpers and conventions for using as a base view for ampersand.js apps.
What does it do?
Part of the Ampersand.js toolkit for building clientside applications.
npm install ampersand-view
Nothing special is required, just use AmpersandView
in the same way as you would Backbone.View:
var MyView = AmpersandView.extend({
initialize: function () { ... },
render: function () { ... }
});
Full description of the available binding types can be found here: https://github.com/henrikjoreteg/dom-bindings#binding-types
A few examples below:
var MyView = AmpersandView.extend({
// set a `template` property of your view. This can either be
// a function that returns an HTML string or just a string if
// no logic is required.
template: '<li><a><span class="userName"></span></a></li>',
// Simple declarative bindings
// The key is the name of the model property
bindings: {
// the value is a selector, by default a text binding is assumed
// this would keep the model's `name` attribute in the span, even
// if it's changed.
'model.name': '.userName',
// 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.
},
// As you might have guessed, you can bind to any attribute you want
'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: {
type: 'booleanAttribute',
selector: 'input',
name: 'checked'
},
// 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: [
{
type: 'text',
selector: '.username'
},
{
type: 'class',
selector: 'li'
},
// btw, also works for selectors that match multiple elements
{
type: 'booleanAttribute',
selector: '.username, .thing'
}
]
},
render: function () {
// method for rendering the view's template and binding all
// the model properties as described by `textBindings` above.
// You can also bind other attributes, and if you're using
// ampersand-model, you can bind derived properties too.
this.renderWithTemplate({what: 'some context object for the template'});
}
});
Note that these are all one-way bindings. People love to talk about the feature of two-way bindings, but from my experience, the vast majority of the time it's not actually something that you want. Why make two different ways of doing the same thing? The most common two-way bindings that people do are for form elements, which, is super easy to do with the events hash. Plus, then it's very easy to configure exactly when you want the user action to actually change the model.
Having said that, we may enable it anyway, in a future release, that's still up for discussion.
Often you want to render some other subview within a view. The trouble is that when you remove the parent view, you also want to remove all the subviews.
AmpersandView has two convenience method for handling this that's also used by renderCollection
to do cleanup.
It looks like this:
var AmpersandView = require('ampersand-view');
// This can be *anything* with a `remove` method
// and an `el` property... such as another ampersand-view
// instance.
// But you could very easily write other little custom views
// that followed the same conventions. Such as custom dialogs, etc.
var SubView = require('./my-sub-view');
module.exports = AmpersandView.extend({
render: function () {
// this takes a view instance and either an element, or element selector
// to draw the view into.
this.renderSubview(new Subview(), '.someElementSelector');
// There's an even lower level api that `renderSubview` usees
// that will do nothing other than call `remove` on it when
// the parent view is removed.
this.registerSubview(new Subview());
}
})
registerSubview also, stores a reference to the parent view on the subview as .parent
Note that this is a fork of Backbone's view so most of the public methods/properties here still exist: http://backbonejs.org/#View.
AmpersandView.extend([properties])
Get started with views by creating a custom view class. Ampersand views have a sane default render function, which you don't necessarily have to override, but you probably will wish to specify a template
, your declarative event handlers and your view bindings.
var PersonRowView = AmpersandView.extend({
template: "<li> <span role='name'></span> <span role='age'></span> <a role='edit'>edit</a> </li>",
events: {
"click [role=edit]": "edit"
},
bindings: {
"model.name": {
type: 'text',
role: 'name'
},
"model.age": {
type: 'text',
role: 'age'
}
},
edit: funciton () {
//...
}
});
AmpersandView.extend({ template: "<div><input></div>" })
The .template
is a property for the view prototype. It should either be a string of HTML or a function that returns a string of HTML or a DOM element. It isn't required, but it is used as a default for calling renderWithTemplate
.
The important thing to note is that the returned string/HTML should not have more than one root element. This is because the view code assumes that it has one and only one root element that becomes the .el
property of the instantiated view.
For more information about creating, and compiling templates, read the templating guide.
AmpersandView.extend({ autoRender: true })
The .autoRender
property lets you optionally specify that the view should just automatically render with all the defaults. This requires that you at minimum specify a template string of function.
By setting autoRender: true
the view will simply call .renderWithTemplate
for you (after your initialize
method if present). So for simple views, if you've got a few bindings and a template your whole view could just be really and declarative like this:
var AmpersandView = require('ampersand-view');
module.exports = AmpersandView.extend({
autoRender: true,
template: '<div><span id="username"></span></div>',
bindings: {
name: '#username'
}
});
Note: if you are using a template function (and not a string) the template function will get called with a context argument that looks like this, giving you access to .model
, .collection
and any other props you have defined on the view from the template.
this.renderWithTemplate(this, this.template);
AmpersandView.extend({ events: { /* ...events hash... */ } })
The events hash allows you to specify declarative callbacks for DOM events within the view. This is much clearer and less complex than calling $('selector').on('click', ...)
or el.addEventListener('click', ...)
everywhere.
{"event selector": "callback"}
.selector
causes the event to be bound to the view's root element (this.el
).Using the events hash has a number of benefits over manually binding events during the render
call:
this
continues to refer to the view object.var DocumentView = AmpersandView.extend({
events: {
//bind to a double click on the root element
"dblclick" : "open",
//bind to a click on an element with both 'icon' and 'doc' classes
"click .icon.doc" : "select",
"contextmenu .icon.doc" : "showMenu",
"click .show_notes" : "toggleNotes",
"click .title .lock" : "editAccessLevel",
"mouseover .title .date" : "showTooltip"
},
open: function() {
window.open(this.model.viewer_url);
},
select: function() {
this.model.selected = true;
},
//...
});
The bindings hash gives you a declarative way of specifying which elements in your view should be updated when the view's model is changed.
For a full reference of available binding types see: henrikjoreteg/dom-bindings.
For example, with a model like this:
var Person = AmpersandModel.extend({
props: {
name: 'string',
age: 'number',
avatarURL: 'string'
},
session: {
selected: 'boolean'
}
});
and a template like this:
<!-- templates.person -->
<li>
<img role="avatar">
<span role="name"></span>
age: <span role="age"></span>
</li>
you might have a binding hash in your view like this:
var PersonView = AmpersandView.extend({
templates: templates.person,
bindings: {
'model.name': {
type: 'text',
role: 'name'
},
'model.age': '[role=age]', //shorthand of the above
'model.avatarURL': {
type: 'attribute',
name: 'src',
role: 'avatar'
},
//no selector, selects the root element
'model.selected': {
type: 'booleanClass',
name: 'active' //class to toggle
}
}
});
view.el
All rendered views have a single DOM node which they manage, which is acessible from the .el
property on the view. Allowing you to insert it into the DOM from the parent context.
var view = new PersonView({ model: me });
view.render();
document.querySelector('#viewContainer').appendChild(view.el);
new AmpersandView([options])
The default AmpersandView
constructor accepts an optional options
object, and:
model
, collection
, el
.events
hash.bindings
hash.subviews
hash.initialize
passing it the options hash.autoRender
is true and a template is defined.Typical use-cases for the options hash:
el
already in the DOM, pass it as an option: new AmpersandView({ el: existingElement })
.initialize
function in the extend call, rather than modifying the constructor, it's easier.new AmpersandView([options])
Called by the default view constructor after the view is initialized. Overwrite initialize in your views to perform some extra work when the view is initialized. Initially it's a noop:
var MyView = AmpersandView.extend({
initialize: function (options) {
console.log("The options are:", options);
}
});
var view = new MyView({ foo: 'bar' });
//=> logs 'The options are: {foo: "bar"}'
view.render()
Render is a part of the Ampersand View conventions. You can override the default view method when extending AmpersandView if you wish, but as part of the conventions, calling render should:
this.el
property if the view doesn't already have one, and populate it with your view templatethis.el
attribute, render should either populate it with your view template, or create a new element and replace the existing this.el
if it's in the DOM tree.The default render looks like this:
render: function () {
this.renderWithTemplate(this);
return this;
}
view.renderCollection(collection, ItemView, containerEl, [viewOptions])
collection
{Backbone Collection} The instantiated collection we wish to render.itemViewClass
{View Constructor} The view constructor that will be instantiated for each model in the collection. This view will be instantiated with a reference to the model and collection and the item view's render
method will be called with an object containing a reference to the containerElement as follows: .render({containerEl: << element >>})
.containerEl
{Element} The element that should hold the collection of views.viewOptions
{Object} [optional] Additional options
viewOptions
{Object} Options object that will get passed to the initialize
method of the individual item views.filter
{Function} [optional] Function that will be used to determine if a model should be rendered in this collection view. It will get called with a model and you simply return true
or false
.reverse
{Boolean} [optional] Convenience for reversing order in which the items are rendered.This method will maintain this collection within that container element. Including proper handling of add, remove, sort, reset, etc.
Also, when the parent view gets .remove()
'ed any event handlers registered by the individual item views will be properly removed as well.
Each item view will only be .render()
'ed once (unless you change that within the item view itself).
// some view for individual items in the collection
var ItemView = AmpersandView.extend({ ... });
// the main view
var MainView = AmpersandView.extend({
template: '<section class="page"><ul class="itemContainer"></ul></section>',
render: function (opts) {
// render our template as usual
this.renderWithTemplate(this);
// call renderCollection with these arguments:
// 1. collection
// 2. which view to use for each item in the list
// 3. which element within this view to use as the container
// 4. options object (not required):
// {
// // function used to determine if model should be included
// filter: function (model) {},
// // boolean to specify reverse rendering order
// reverse: false,
// // view options object (just gets passed to item view's `initialize` method)
// viewOptions: {}
// }
this.renderCollection(this.collection, ItemView, this.$('.itemContainer')[0], opts);
return this;
}
})
view.registerSubview(viewInstance)
view.renderSubview(viewInstance, containerEl)
.remove()
, .render()
and an .el
property that is the DOM element for that view. Typically this is just an instantiated view..container
. If a string is passed human view runs this.$("YOUR STRING")
to try to grab the element that should contain the sub view.This method is just sugar for the common use case of instantiating a view and putting in an element within the parent.
It will:
view.parent
will be available when your subview's render
method gets calledrender()
methodvar view = AmpersandView.extend({
template: '<li><div class="container"></div></li>',
render: function () {
this.renderWithTemplate();
//...
var model = this.model;
this.renderSubview(new SubView({
model: model
}), '.container');
//...
}
});
view.renderWithTemplate([context], [template])
context
{Object | null} [optional] The context that will be passed to the template function, usually it will be passed the view itself, so that .model
, .collection
etc are available.template
{Function | String} [optional] A function that returns HTML or a string of HTML.This is shortcut for the default rendering you're going to do in most every render method, which is: use the template property of the view to replace this.el
of the view and re-register all handlers from the event hash and any other binding as described above.
var view = AmpersandView.extend({
template: '<li><a></a></li>',
bindings: {
'name': 'a'
},
events: {
'click a': 'handleLinkClick'
},
render: function () {
// this does everything
// 1. renders template
// 2. registers delegated click handler
// 3. inserts and binds the 'name' property
// of the view's `this.model` to the <a> tag.
this.renderWithTemplate();
}
});
view.get('.classname')
Runs a querySelector
scoped within the view's current element (view.el
), returning the first matching element in the dom-tree.
var view = AmpersandView.extend({
template: '<li><img role="avatar" src=""></li>',
render: function () {
this.renderWithTemplate(this);
// cache an element for easy reference by other methods
this.imgEl = this.get("[role=avatar]");
return this;
}
});
view.getByRole('rolename')
A convenience method for retrieving an element from the current view by role. Using the role attribute is a nice way to separate javascript view hooks/bindings from class/id selectors that are being used by css:
var view = AmpersandView.extend({
template: '<li><img class='avatar-rounded' role="avatar" src=""></li>',
render: function () {
this.renderWithTemplate(this);
// cache an element for easy reference by other methods
this.imgEl = this.getByRole('avatar');
return this;
}
});
view.getAll('.classname')
Runs a querySelectorAll
scoped within the view's current element (view.el
), returning all the matching elements in the dom-tree.
view.cacheElements(hash)
A shortcut for adding reference to specific elements within your view for access later. This is avoids excessive DOM queries and makes it easier to update your view if your template changes.
In your render
method. Use it like so:
render: function () {
this.renderWithTemplate(this);
this.cacheElements({
pages: '#pages',
chat: '#teamChat',
nav: 'nav#views ul',
me: '#me',
cheatSheet: '#cheatSheet',
omniBox: '[role=omnibox]'
});
return this;
}
Then later you can access elements by reference like so: this.pages
, or this.chat
.
view.listenToAndRun(object, eventsString, callback)
Shortcut for registering a listener for a model and also triggering it right away.
view.remove()
Removes a view from the DOM, and calls stopListening
to remove any bound events that the view has listenTo
'd.
view.delegateEvents([events])
Creates delegated DOM event handlers for view elements on this.el
. If events
is omitted, will use the events
property on the view.
Generally you won't need to call delegateEvents
yourself, if you define an event
hash when extending AmpersandView, delegateEvents
will be called for you when the view is initialize.
Events is a hash of {"event selector": "callback"}*
Will unbind existing events by calling undelegateEvents
before binding new ones when called. Allowing you to switch events for different view contexts, or different views bound to the same element.
{
'mousedown .title': 'edit',
'click .button': 'save',
'click .open': function (e) { ... }
}
view.undelegateEvents()
Clears all callbacks previously bound to the view with delegateEvents
.
You usually don't need to use this, but may wish to if you have multiple views attached to the same DOM element.
<body>
case.getByRole
work even if role
attribute is on the root element. Throws an error if your view template contains more than one root element..parent
before it calls .render()
on the subview.getByRole
method.empty()
in renderCollection. (fixes: https://github.com/HenrikJoreteg/ampersand-view/issues/13)parent
reference to subviews registered via registerSubviewWhy yes! So glad you asked :)
Open test/test.html
in a browser to run the QUnit tests.
Follow @HenrikJoreteg on twitter and check out my recently released book: human javascript which includes a full explanation of this as well as a whole bunch of other stuff for building awesome single page apps.
MIT
FAQs
A smart base view for Backbone apps, to make it easy to bind collections and properties to the DOM.
The npm package ampersand-view receives a total of 8,163 weekly downloads. As such, ampersand-view popularity was classified as popular.
We found that ampersand-view demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 8 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.