The ƒ Manual


Introduction

Inheritance

Common Tasks

Best Practices

Miscellaneous

Introduction


What is ƒ?

ƒ is a component framework for JavaScript applications that promotes code-reuse and frees you from writing boilerplate code.

ƒ relies heavily on Backbone and jQuery, and is a perfect companion for your templating language of choice.

Buzzword soup: Models, views, and what?

Let's avoid the buzzword game and explain we we're talking about.

Models

Models are a local representation of an object or database entry. ƒ uses vanilla Backbone models.

Templates

Templates are HTML structure and template code. Templates control the presentation of your models on the page. ƒ uses vanilla Handlebars templates.

Views

Views render a model with a given template, putting your data into your HTML structure. Views can be shown or hidden. ƒ uses vanilla Backbone views.

Components

Components are logical units of application code. They have methods, models, views, and sub-components. A component consists of many sub-components that it connects together by listening to events from one component and calling methods on another.

Inheritance


How does ƒ help you stay classy?

ƒ uses PseudoClass, an OOP framework that lets you create building blocks that you can extend, mixin, and re-use.

You can use new Class(descriptor) to create a class based on the descriptor object you pass to it.

Properties of the descriptor object

  • toString A string literal or a function that returns a string. ƒ uses this to refer to sub-components.
  • extend The object from which your new class should inherit. Your new class will inherit methods and properties from the class you extend.
  • construct optional method that sets up the object for use. construct is called automatically when an instance of your class is instantiated. The parent class' construct method is always called before the child class.
  • destruct optional teardown method that should remove your object from the DOM and free memory. The parent class' destruct method is automatically called after the child class'.
  • init initialization method that is called after all constructors have been called.
  • * Any other properties of the descriptor are added to the prototype of the new class. Methods, properties, and constants should be defined here.

Defining a class

Just call new Class(descriptor) with a descriptor that describes your class.

var Widget = new Class({ toString: 'Widget', construct: function() { this.$el = $(document.createElement(this.tag)); }, destruct: function() { this.$el.remove(); }, tag: 'div', show: function() { this.$el.show(); }, hide: function() { this.$el.hide(); } });

Extending a class

Set descriptor.extend to the class you would like to inherit from.

To override a method, simply define the method in the prototype of the child class.

var Panel = new Class({ toString: 'Panel', extend: Widget, show: function() { this.$el.fadeIn(); } });

Alternatively, you can use the static extend method of any class:

var Panel = Widget.extend({ toString: 'Panel', show: function() { this.$el.fadeIn(); } });

Calling superclass methods

Add _super as the first argument to the child class' method, then call it using _super.call(this, 'arg1') or _super.apply(this, arguments).

var Widget = new Class({ toString: 'Button', extend: Widget, tag: 'button', hide: function(_super) { _super.apply(this, arguments); this.$el.attr('disabled', true); } });

For more information, view the JSDoc for Class.

Common Tasks


Add ƒ to your project

  1. Get ƒ. Download a ZIP and extract it.
    Download

  2. Add the files to your project. Just dump F.js into your js folder.
  3. Add the script include. Preferably to the <head> tag. <script src="js/F.js"></script>
  4. Get namespaced. If your app doesn't already have a namespace, create one. var App = {};

Fetch and display a model

ƒ provides F.ModelComponent, which encapsulates some of the common boilerplate code you'd otherwise write when dealing with models.

  1. Define your model. Your model will need to know where to fetch and save itself. App.Models.Item = Backbone.Model.extend({ urlRoot: 'api/items' });
  2. Create a template to display the model's details. Your template should print the markup and each of the fields you want to display. App.Templates.ItemDetails = Handlebars.compile( '<h2>{{name}}</h2>'+ '<p><a href="mailto:{{email}}">{{email}}</a></p>'+ '{{#if phone}}<p>{{phone}}</p>{{/if}}' );
  3. Extend F.ModelComponent. Provide Model and F.ModelComponent will provide methods. App.ItemDetails = new Class({ toString: 'ItemDetails', extend: F.ModelComponent, construct: function(options) { // Let users specify options; extend defaults // so users can specify a new template or model. options = _.extend({ model: this.Model, template: this.Template }, options); // Create a view this.view = new this.View(options); }, // The model we'll be fetching and displaying Model: App.Models.Item, View: F.View.extend({ events: { 'click .sendMessage': 'doSend' } }), Template: App.Templates.ItemDetails });
  4. Display a model. Give the show() method an ID and F.ModelComponent will fetch it from the server for you. You can also directly pass the model to show(), which is useful when editing a model you have already loaded (such as part of a list). // Generally, you'll add the editor as a component // For this example, we'll just create an instance app.details = new App.ItemDetails(); // Display the model with the ID=10, fetching it from the server first app.details.show({ id: 10 }); // If you already have the model, call show and pass it directly var tempModel = new App.Models.Item({ name: 'Larry Davis', email: '[email protected]' }); app.details.show({ model: tempModel });

See the Contacts example for a live demo.

Edit a model and save changes to the server

ƒ provides F.FormComponent, which handles fetching models from the server, rendering the corresponding edit form, and asynchronously saving changes.

  1. Define your model. Your model will need to know where to fetch and save itself. App.Models.Item = Backbone.Model.extend({ urlRoot: 'api/items' });
  2. Create a template for your form. You'll need to create a template that has input fields for each one of the properties you want to expose for editing. App.Templates.ItemEditor = Handlebars.compile( '<label>Name</label>'+ '<input type="text" name="name" value="{{name}}">'+ '<label>E-mail</label>'+ '<input type="text" name="email" value="{{email}}">'+ '<label>Phone</label>'+ '<input type="text" name="phone" value="{{phone}}">'+ '<button type="submit">Save</button>' );
  3. Extend F.FormComponent. Provide Model and Template, and F.FormComponent will do the rest. App.ItemEditor = new Class({ toString: 'ItemEditor', extend: F.FormComponent, // Indicate the model you'll be loading and saving Model: App.Models.Item, Template: App.Templates.ItemEditor });
  4. Load a model for editing. Give the show() method an ID and F.ModelComponent will fetch it from the server for you. You can also directly pass the model to show(), which is useful when editing a model you have already loaded (such as if a user clicked an Edit button from the details view). // Generally, you'll add the editor as a component // For this example, we'll just create an instance app.editor = new App.ItemEditor(); // Do something on a successful save app.editor.on('saved', function() { alert('Changes saved!'); }); // Do something when the save fails app.editor.on('saveFailed', function() { alert('ERROR: Failed to save changes!'); }); // Edit the model with the ID=10, fetching it from the server first app.editor.show({ id: 10 }); // If you already have the model, call show and pass it directly var tempModel = new App.Models.Item({ name: 'Larry Davis', email: '[email protected]' }); app.editor.show({ model: tempModel });

See the Contacts example for a live demo.

Fetch and display a collection of models

ƒ provides F.ListComponent, which handles fetching a collection of models from the server and rendering the corresponding list.

  1. Define your model and collection. Your collection needs to know where to fetch itself from and what models it consists of. App.Models.Item = Backbone.Model.extend({ urlRoot: 'api/items' }); App.Collections.Items = Backbone.Model.extend({ url: 'api/items', model: App.Models.Item });
  2. Create a template for your list item. You'll need to create a template that prints minimal information about your model. App.Templates.ListItem = Handlebars.compile( '<strong>{{name}}</strong>' );
  3. Extend F.ListComponent. Provide Collection and ItemTemplate, and F.ListComponent will do the rest. App.ItemList = new Class({ toString: 'ItemList', extend: F.ListComponent, // Indicate the collection you'll be loading and rendering Collection: App.Models.Item, ItemTemplate: App.Templates.ListItem });
  4. Load the collection. Just call show(). // Generally, you'll add the editor as a component // For this example, we'll just create an instance app.list = new App.ItemList(); // Do something when an item is clicked app.list.on('itemSelected', function() { alert('Changes saved!'); }); // Show the list. show() will call load() if necessary app.list.show();
  5. Filter the collection server-side. Just call load(params), where params is an object representing the GET parameters your server takes to perform filtering. // Generally, you'll add the editor as a component // For this example, we'll just create an instance app.list = new App.ItemList(); // Do something when an item is clicked app.list.on('itemSelected', function() { alert('Changes saved!'); }); // Load the list with custom parameters app.list.load({ query: 'shoes' });

See the Contacts example for a live demo.

Best Practices


Breaking up your application into components

Coming soon.

Why have nested components?

Coming soon.

What should be a component and what shouldn't?

Coming soon.

Enabling code-reuse with good component design

Designing for one use case and moving on is easy the first time, but when you're writing the same boilerplate code for every part of your project, it's time to go back to the drawing board.

Look for opportunities to abstract

That block of code you keep pasting into every component should really be part of a parent class from which you inherit.

App.Hustler = new Class({ toString: 'Hustler', extend: F.Component, construct: function(options) { this.hustle(); }, hustle: function() { // This line of code is boilerplate; // that is, we have it in all related components alert("I'm a "+this.toString()); } }); App.HustlerBaby = new Class({ toString: 'HustlerBaby', // Inherit from Hustler so we get hustle() extend: App.Hustler, hustle: function(_super) { // Instead of re-writing the boilerplate code, // call the parent class' hustle method _super.apply(this, arguments); // Do additional stuff alert('I just want you to know'); } });

Put it in the prototype

Declare your models, views, and templates in the prototype of the components you create. This lets child classes easily override them.

DOM event delegation

When you have many items that all need to handle the same event, instead of adding one listener for each item, add a listener to the parent container. This helps performance when your list has thousands of items.

But how do I know which item got the click? Use evt.target to determine the DOM node that got the click. If that node is associated with some data (a model, for instance), then use $(node).data('model', theModel) to store and retrieve the model. F.ListComponent will store the model in the node's data for you. App.PhotoGrid = new Class({ toString: 'PhotoGrid', extend: F.ListComponent, Collection: Backbone.Collection.extend(), ItemTemplate: Handlebars.compile( '<img src="{{url}}">' ), // Extend ListComponent, override its events View: F.ListComponent.prototype.ListView.extend({ tag: 'ul', className: 'photoGrid', events: { 'mouseover li': 'handleMouseover' } }), handleMouseover: function(evt) { var target = $(evt.target); var model = target.data('model'); console.log(model.name+' has the mouse over it!'); } });

Miscellaneous


How do I type ƒ?

Yes, ƒ is a valid JavaScript variable name, and you use it instead of F if you're super hip.

Mac: Option + f
Windows: alt + 1 5 9
Maintained by lazd