@drewfyock
Ampersand.js
Minimalistic Approach
to not so minimalistic problems
@drewfyock
Ampersand.jsby
@drewfyock
Core Team
@henrikjoreteg
@philip_roberts @lancestout @lynnandtonic
@lukekarrys @wraithgar @kamilogorek
@drewfyock
Community Team
@bear
@drewfyock @aaronmccall
@remko @fox @mmacaula
@drewfyock
Overview
@drewfyock
@drewfyock
State
ampersand-state
@drewfyock
var Person = AmpersandState.extend({
props: {
firstName: 'string',
lastName: 'string'
},
session: {
signedIn: ['boolean', true, false],
},
derived: {
fullName: {
deps: ['firstName', 'lastName'],
fn: function () {
return this.firstName + ' ' + this.lastName;
}
}
}
});
@drewfyock
Models
ampersand-model
ampersand-state & ampersand-sync
@drewfyock
Collections
@drewfyock
ampersand-collection / ampersand-subcollection / ampersand-rest-collection
ampersand-collection
@drewfyock
ampersand-subcollection
ampersand-collection & ampersand-collection-underscore-mixin
@drewfyock
var WidgetCollection = require('./mycollection');
var SubCollection = require('ampersand-subcollection');
var widgets = new WidgetCollection();
widgets.fetch();
var favoriteWidgets = new SubCollection(widgets, {
where: {
awesome: true
},
comparator: function (model) {
return model.rating;
}
});
@drewfyock
ampersand-rest-collection
ampersand-collection & ampersand-collection-rest-mixin & ampersand-collection-underscore-mixing
@drewfyock
Views
@drewfyock
ampersand-view
ampersand-state & ampersand-collection-view & ampersand-dom-bindings
@drewfyock
var PersonRowView = AmpersandView.extend({
template: "<li><span data-hook='name'></span><span data-hook='age'></span><a data-
hook='edit'>edit</a></li>",
events: {
"click [data-hook=edit]": "edit"
},
bindings: {
"model.name": {
type: 'text',
hook: 'name'
},
"model.age": {
type: 'text',
hook: 'age'
}
},
subviews: {
personSubview: {...}
},
edit: function () {...}
});
@drewfyock
ampersand-view-switcher
ampersand-view
@drewfyock
var pageSwitcher = new ViewSwitcher(pageContainer, {
waitForRemove: true,
hide: function (oldView, newView, cb) {
oldView.el.classList.add('animateOut');
setTimeout(cb, 1000);
},
show: function (newView, oldView) {
document.title = newView.pageTitle || 'app name';
document.body.scrollTop = 0;
app.currentPage = newView;
newView.el.classList.add('animateIn');
}
});
@drewfyock
ampersand-form-viewampersand-view
@drewfyock
var FormView = require('ampersand-form-view');
var InputView = require('ampersand-input-view');
var AwesomeFormView = new FormView({
submitCallback: function (obj) {
console.log('form submitted! Your data:', obj);
},
validCallback: function (valid) {
if (valid) {
console.log('The form is valid!');
} else {
console.log('The form is not valid!');
}
},
fields: [
new InputView({
name: 'client_name',
label: 'App Name',
placeholder: 'My Awesome App',
value: 'hello',
tests: [
function (val) {
if (val.length < 5) return "Must be 5+ characters.";
}
]
})
]
});
@drewfyock
ampersand-input-view
ampersand-array-input-view
@drewfyock
ampersand-select-view
ampersand-checkbox-view
@drewfyock
View Bindings
ampersand-dom-bindingsampersand-dom
@drewfyock
text
@drewfyock
text
class
@drewfyock
text
class
attribute
@drewfyock
text
class
attribute
value
@drewfyock
text
class
attribute
value
booleanClass
@drewfyock
text
class
attribute
value
booleanClass
booleanAttribute
@drewfyock
text
class
attribute
value
booleanClass
booleanAttribute
toggle
@drewfyock
text
class
attribute
value
booleanClass
booleanAttribute
toggle
switch
@drewfyock
text
class
attribute
value
booleanClass
booleanAttribute
toggle
switch
innerHTML
@drewfyock
var View = require('ampersand-view');
var templates = require(‘../templates’);
module.exports = View.extend({
template: templates.includes.person,
bindings: {
'model.fullName': '[data-hook=name]',
'model.avatar': {
type: 'attribute',
hook: 'avatar',
name: 'src'
},
'model.editUrl': {
type: 'attribute',
hook: ‘action-edit',
name: 'href'
},
'model.viewUrl': {
type: 'attribute',
hook: 'name',
name: 'href'
}
},
events: {
'click [data-hook=action-delete]': 'handleRemoveClick'
},
handleRemoveClick: function () {
this.model.destroy();
}
});
@drewfyock
Router
@drewfyock
var AppRouter = AmpersandRouter.extend({
routes: {
"help": "help", // #help
"search/:query":"search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {...},
search: function(query, page) {...}
});
@drewfyock
Mixins
ampersand-class-extend
@drewfyock
ampersand-collection-rest-mixin
@drewfyock
ampersand-collection-rest-mixin
ampersand-collection-underscore-mixin
@drewfyock
ampersand-collection-rest-mixin
ampersand-collection-underscore-mixin
ampersand-domthing-mixin
@drewfyock
ampersand-collection-rest-mixin
ampersand-collection-underscore-mixin
ampersand-domthing-mixin
ampersand-react-mixin
@drewfyock
Ampersand CLI
@drewfyock
Starting a new app
@drewfyock
Starting a new app
Generating form, view, model or collection
@drewfyock
Starting a new app
Generating form, view, model or collection
Generating models from JSON
@drewfyock
Starting a new app
Generating form, view, model or collection
Generating models from JSON
Generating forms from models
@drewfyock
Starting a new app
Generating form, view, model or collection
Generating models from JSON
Generating forms from models
Configuring the generated code
@drewfyock
// .ampersandrc
{
// default framework to be prompted with, options are express or hapi
framework: 'hapi',
indent: 4,
view: '', // default template
router: '', // default template
model: '', // default template
page: '', // default template
collection: '', // default template
clientfolder: 'client',
viewfolder: 'views',
pagefolder: 'pages',
modelfolder: 'models',
formsfolder: 'forms',
collectionfolder: 'models',
// whether to create collection when making a model
makecollection: true,
// if it was called without the 'gen' argument we're building a new one
// so we won't look for an application root
approot: '', // starts walking up folders looking for package.json
f: false, // overwrite
force: false, // overwrite flag, longform
quotes: 'single' // can be 'single' or 'double'
};
@drewfyock
Philosophy
@drewfyock
"Optimize for change,
it's the only constant."
@HenrikJoreteg
10 Sep 2014
@drewfyock
It's not about
solving a problem,
it's about how you
approach it
@drewfyock
Vendor lock-in
@drewfyock
It's easier to learn only things you need to know
@drewfyock
Everything
is a
separate commonjs
module
@drewfyock
Everything
is
hosted on npm
@drewfyock
Everything
should be
fully tested
@drewfyock
Everything
has a
separate git repo
with
separate git issues
@drewfyock
@drewfyock
Everything
does just
one thing
and does it
well
@drewfyock
Leverage existing solutions
and
don't reinvent the wheel
@drewfyock
@drewfyock
Live demo
@drewfyock
Summary
@drewfyock
Why we chose Ampersand at Bessemer:
CommonJS by default
Flexible and modular
Based on Backbone
Just the right amount of magic
Incredibly easy to integrate with Express or Hapi
Support on Gitter
@drewfyock
Why you might not choose Ampersand:
Still fairly new
Moonboots - Jade
Not as “full featured" as Angular or Ember
May have to roll-your-own for specific components
@drewfyock
http://ampersandjs.com/contribute
http://issues.ampersandjs.com
https://github.com/ampersandjs
@drewfyock
Thank You
@drewfyock @mikesabatini
http://bessemeralliance.com

Ampersandjs

Editor's Notes

  • #6 An overview of ampersand itself and our philosophy. How we do things and why do them in a specific way.
  • #7 "A highly modular, loosely coupled, non-frameworky framework for building advanced JavaScript apps." Its made of models, collections, views, router - nothing unusual. I know it MAY look not so different then other solutions, but just bear with me, I'll walk you through basics first.
  • #9 dataTypes: props / session / derived extraProperties / children / accessed by either set/get or directly thanks to defineProperty
  • #10 mix of ampersand-state and ampersand-sync sync, save, fetch, destroy
  • #12 Just a place to store all your models of the same type On its own it doesn’t do much, you need rest/underscore mixin adds ES5 methods like filter, map, reduce, some
  • #14 just a subset of your main collection where, filter, filters, watched, comparator, limit, offset configure, addFilter, removeFilter, clearFilters
  • #15 fetch, getOrFetch, fetchById, create, sync contains all underscore methods
  • #18 bindings, events, template (string/function), autoRender, renderCollection, query , queryByHook, listenToAndRun, registerSubview/renderSubview/subviews, waitFor in subViews
  • #20 show/hide/waitForRemove/empty
  • #22 el, submitCallback, validCallback, fields (can be of course registered as registerSubview) Automatically shows/hides error messages based on tests Live validates to always report if valid state but only shows messages when sane to do so Will not show error messages pre-submit or it's never had a valid value. This lets people tab-through a form without triggering a bunch of error messages.
  • #23 view module for intelligently rendering and validating input array: same as input view but for multiple inputs (if you want array of answers) with add/remove buttons
  • #24 Dropdown and checkboxes
  • #25 role / hook accessibility implications / ARIA roles role/data-js/data-bind/data-hook/data-action/rel/name/classes
  • #26 sets/maintains textContent of selected element. treats undefined, null, and NaN as ''
  • #27 sets and maintains single class as string that matches value of property
  • #28 sets the whole attribute to match value of property.
  • #29 sets the value of the element to match value of the property. works well for input, select, and textarea
  • #30 add/removes class based on boolean interpretation of property name
  • #31 add/removes attribute based on boolean interpretation of property name
  • #32 toggles existence of entire element (uses a comment node as placeholder if gone) based on boolean interpretation of property name.
  • #33 Toggles existence of multiple items based on value of property.
  • #34 renders innerHTML, can be a string or DOM, based on property value of model
  • #35 data bindings in action setting of avatar src, edit/view URLs
  • #37 pretty straightforward, watch your routes and execute appropriate function, handle back button, redirects etc. pushState and hashChange
  • #38 This is where fun parts begin! Helper function for setting up prototype chains for subclasses. thanks to it you can do `.extend()` anything
  • #41 Make automatic dom binding possible (extends renderWithTemplate function)
  • #42 you can even create things like react mixin, that Philip Roberts is I believe currently working on ;)
  • #43 And last but not least
  • #44 full app with api, structure and so on hapi/express
  • #50 To build great products, you want to use them yourself first We wanted to create a simple base, a separate pieces that can help you solve your problems, not magically solve them for you.
  • #51 I couldn't say that better.
  • #52 Because how you approach it, determines how you'll solve your future problems and you for sure WILL have them sooner or later.
  • #53 tangled huge parts, hard to modify for your needs
  • #54 Its easier to lean few small apis than one huge api
  • #55 There're no questions how to integrate it with gulp, grunt or anything like that. All you need is browserify.
  • #57 it's much easier to test small pieces then one huge framework still working on it
  • #58 publish separately its hard to change things, because it requires bumping version in package managers as well
  • #59 forks, localStorage/realtime with sockets and so on
  • #60 unix philosophy if you've a problem that you solve over and over again – you should definitely abstract it create separate module or create mixin
  • #61 npm, packages, common tools
  • #62 and because of that we have ampersandjs tools page where you can find solutions for already defined, small problems and where you can share your own modules or mixins
  • #63 CLI, new app, generate model
  • #65 - Why we use it + CommonJS by default * Simple, familiar * Keeps the same paradigm in server and client + Flexible and module * Lightweight * Use the parts you want, omit the ones you don't * Each module is small, easy to grok, source is extremely approachable + Based in Backbone * If you know Backbone, you know ampersand - Models, Views, Collections * Flexible, minimally prescriptive - Allows for template engine of your choice * Familiar if you've used Backbone / Marionette + Just the right amount of magic * ampersand-state is the crown jewel - Both models and views are subclasses - Properties are cleanly declared and manipulated * View bindings prevent writing a lot of DOM-maipulation code - Focus on your organization and logic * Javascript only solution - Doesn't get into your markup, which should be agnostic (i.e. Angular?) + Incredibly easy to integrate with express or hapi * Ever try setting up r.js? This is cake compared
  • #66 - Why you don't want to use it + New, changing regularly + Moonboots... seriously? wtf? + Not as full "featured" as angular or ember + May have to roll-your-own for specific components + Too much global state * This was going to be fixed, was it?