HappyRhino gives structure to client-side web applications by bringing a complete build system
with a a rich API similar to Backbone.js.
hr.js is an open-source component of FriendCode.
Current version is <%- hr.configs.version %>, code is disponible on GitHub : FriendCode/hr.js.
Checkout a list of web-applications which are using hr.js :
- FriendCode, Realtime collaborative code editor built for the cloud era.
- Doks.io, Searching documentation made easy.
- TV.js, Apple TV for Torrent Streaming in JS (Node/Chrome).
- Reportr, Your life's personal dashboard.
- GitRap, Distributed Git based Forums.
<%= view.component("counter", {
prefix: "Documentation updated ",
suffix: " ago.",
from: new Date(hr.configs.revision)
}) %>
Simply install hr.js using NPM :
npm install -g hr.js
or
npm install -g git+https://github.com/FriendCode/hr.js.git#master
HappyRhino lets you create a simple base application using :
hr.js new -d my_app
Build and run your application using:
hr.js all -d my_app
And here is your first client-side application with HappyRhino!
hr.js as been build to bring a real structure to client-side application code.
<%= view.component("code", {code: "build/structure.txt"}) %>
An application in hr.js need a base build configuration, this configuration is stored in a "build.js" file:
<%= view.component("code", {code: "build/build.js"}) %>
Here is a list of all the differents options for build process :
<%= view.component("code", {code: "build/options.js"}) %>
For building, simply run in a command line tool : hr.js all
.
You can also only build with : hr.js build
.
Or only run the simple static server with : hr.js run
.
HappyRhino uses require.js as module loader.
<%= view.component("code", {code: "build/module.js"}) %>
Each build use a unique revision timestamp, this revision id is useful for avoiding cache during resources requests.
<%= view.component("code", {code: "build/revision.js", run: true}) %>
The build process can pass arguments to the client side application :
<%= view.component("code", {code: "build/options_args.js"}) %>
And arguments are simply accessible in the client side :
<%= view.component("code", {code: "build/args.js"}) %>
The module Application represent the base for your application, it uses to describe the main entry point of your application and its configurations.
The class Application inherit from ">hr.View
.
">extend hr.Application.extend(properties, [classProperties])
To create an Application class of your own, ">extend hr.Application
, providing instance properties, as well as optional classProperties to be attached directly to the application's constructor function.
<%= view.component("code", {code: "application/extend.js"}) %>
">run application.run()
After extending the hr.Application
class, you need to start your new application.
<%= view.component("code", {code: "application/run.js"}) %>
">title application.title([newValue, absolute])
Get or set the page title, title could be relative to the application (format: @appname - @title
) or absolute (format: @title
). This method use ">head.
<%= view.component("code", {code: "application/title.js"}) %>
">head application.head
Property head
of application let you easily manage page informations such as title, metas, links, crawling.
<%= view.component("code", {code: "application/head.js"}) %>
">extend hr.View.extend(properties, [classProperties])
The first thing who have to do for creating a new view is extending the basic view class.
<%= view.component("code", {code: "view/extend.js"}) %>
Views in hr.js can easily uses templates by defining property template
with the template name and uses templateContext
to return the variables for the template.
For using templates, you need to specify the ressource loader for templates : ">Templating/Loading
.
<%= view.component("code", {code: "view/template.js"}) %>
template
name could alse be given by a function, for example :
<%= view.component("code", {code: "view/template_function.js"}) %>
Render the view is easy using the method render, you can bind the events "render"
(after rendering) or "ready"
(first time rendering).
<%= view.component("code", {code: "view/render.js"}) %>
By default, view use template
for rendering, but you can redefine render
:
<%= view.component("code", {code: "view/render_extend.js"}) %>
Events are written in the format {"event selector": "callback"}
. The callback may be either the name of a method on the view, or a direct function body. Omitting the selector causes the event to be bound to the view's root element (this.el
). By default, delegateEvents is called within the View's constructor for you, so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself.
The events property may also be defined as a function that returns an events hash, to make it easier to programmatically define your events, as well as inherit them from parent views.
Using delegated events provides a number of advantages over manually using jQuery to bind events to child elements during render. All attached callbacks are bound to the view before being handed off to jQuery, so when the callbacks are invoked, this continues to refer to the view object. When delegateEvents
is run, perhaps with a different events hash, all callbacks are removed and delegated afresh — useful for views which need to behave differently when in different modes.
<%= view.component("code", {code: "view/events.js"}) %>
Views in hr can easily manage sub-views using components, these components can be added directly in the templates : ">Learn more about it
Defining a view as a component for temlate is really easy :
<%= view.component("code", {code: "view/component.js"}) %>
hr.js allow view to manage in a simple way templates.
Template can interpolate variables, using <%= … %>
, as well as execute arbitrary JavaScript code, with <% … %>
. If you wish to interpolate a value, and have it be HTML-escaped, use <%- … %>
.
<%= view.component("code", {code: "template/syntax.html"}) %>
Components in template is a great way to manage child views.
Template components have to been registrated using ">hr.View.Template.registerComponent
<%= view.component("code", {code: "template/components.html"}) %>
You can after that easily access to components views in your view :
<%= view.component("code", {code: "template/components.js"}) %>
For using templates, you need to specify to hr how to load a template by name. Templates use the resources namespace "templates" :
<%= view.component("code", {code: "template/load.js"}) %>
The module class is the base for all the modules in hr.js, giving the object the ability to bind and trigger custom named events.
Events do not have to be declared before they are bound, and may take passed arguments.
">extend hr.Class.extend(properties, [classProperties])
To create a class of your own, you extend hr.Class
and provide instance properties, as well as optional classProperties to be attached directly to the constructor function.^>
extend correctly sets up the prototype chain, so subclasses created with extend can be further extended and subclassed as far as you like.
<%= view.component("code", {code: "class/extend.js"}) %>
initialize represents the constructor of the class, object passed to initialize is merged with ">defaults
into options
.
<%= view.component("code", {code: "class/initialize.js", run: true}) %>
defaults let you define the defaults properties of the objects. Properties of the objects are stored into options
.
<%= view.component("code", {code: "class/defaults.js", run: true}) %>
">on object.on(event, callback, [context])
Bind a callback function to an object. The callback will be invoked whenever the event is fired. If you have a large number of different events on a page, the convention is to use colons to namespace them: "poll:start"
, or "change:selection"
. The event string may also be a space-delimited list of several events...
<%= view.component("code", {code: "class/on.js"}) %>
To supply a context value for this when the callback is invoked, pass the optional third argument: object.on('change', this.render, this)
Callbacks bound to the special "all"
event will be triggered when any event occurs, and are passed the name of the event as the first argument. For example, to proxy all events from one object to another:
<%= view.component("code", {code: "class/on_all.js"}) %>
All event methods also support an event map syntax, as an alternative to positional arguments:
<%= view.component("code", {code: "class/on_map.js"}) %>
hr.js alse supports hierarchical events:
<%= view.component("code", {code: "class/on_sub.js", run: true}) %>
">off object.off([event], [callback], [context])
Remove a previously-bound callback function from an object. If no context is specified, all of the versions of the callback with different contexts will be removed. If no callback is specified, all callbacks for the event will be removed. If no event is specified, callbacks for all events will be removed.
<%= view.component("code", {code: "class/off.js"}) %>
">trigger object.trigger(event, [*args])
Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be passed along to the event callbacks.
">triggerOnly object.triggerOnly(event, [*args])
Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be passed along to the event callbacks. But don't trigger parents events.
">once object.once(event, callback, [context])
Just like ">on, but causes the bound callback to only fire once before being removed. Handy for saying "the next time that X happens, do this".
The class Model let you define complex model in your applciation.
">extend hr.Model.extend(properties, [classProperties])
To create a Model class of your own, you extend hr.Model
and provide instance properties, as well as optional classProperties to be attached directly to the constructor function.
<%= view.component("code", {code: "model/extend.js", run: true}) %>
When creating an instance of a model, you can pass in the initial values of the attributes, which will be ">set on the model. If you define an initialize function, it will be invoked when the model is created.
">get model.get(attribute, [default])
Get the current value of an attribute from the model. For example: note.get("author.name")
">set model.set(attributes, [options])
Set a hash of attributes (one or many) on the model. If any of the attributes change the model's state, a "change"
event will be triggered on the model. Change events for specific attributes are also triggered, and you can bind to those as well, for example: change:title
, and change:content
. You may also pass individual keys and values.
<%= view.component("code", {code: "model/set.js", run: true}) %>
">has model.has(attribute)
Returns true
if the attribute is set to a non-null or non-undefined value.
">clear model.clear([options])
Clear the object attributes, options
can contain silent
to not triggering events change and clear.
Return a copy of the model's ">attributes for JSON stringification. This can be used for persistence, serialization, or for augmentation before being sent to the server. The name of this method is a bit confusing, as it doesn't actually return a JSON string — but I'm afraid that it's the way that the JavaScript API for JSON.stringify works.
<%= view.component("code", {code: "model/tojson.js", run: true}) %>
Joints between models let you define complex models and relationship between models.
Property joints
is a map of global key and model constructor function.
<%= view.component("code", {code: "model/extend.js", run: true}) %>
Collections are ordered sets of ">models. You can bind "change"
events to be notified when any model in the collection has been modified, listen for "add"
and "remove"
events, and use a full suite of Underscore.js methods.
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience. This allows you to listen for changes to specific attributes in any model in a collection, for example: documents.on("change:selected", ...)
">extend hr.Collection.extend(properties, [classProperties])
To create a Collection class of your own, extend hr.Collection, providing instance properties
, as well as optional classProperties
to be attached directly to the collection's constructor function.
">model collection.model
Override this property to specify the model class that the collection contains. If defined, you can pass raw attributes objects (and arrays) to ">add and ">reset, and the attributes will be converted into a model of the proper type.
<%= view.component("code", {code: "collection/model.js"}) %>
A collection can also contain polymorphic models by overriding this property with a function that returns a model.
<%= view.component("code", {code: "collection/model_polymorphic.js"}) %>
">toJSON collection.toJSON()
Return an array containing the attributes hash of each model in the collection. This can be used to serialize and persist the collection as a whole. The name of this method is a bit confusing, because it conforms to the JavaScript API for JSON.stringify works.
<%= view.component("code", {code: "collection/tojson.js", run: true}) %>
The class Requests let you create simple or complex http requests in a easy way.
">Requests new hr.Requests(options)
The class Requests is very simple to use.
<%= view.component("code", {code: "requests/class.js", run: true}) %>
The Requests class get some usefull shortcuts class methods such as ">get or ">post
">get hr.Requests.get(url, arguments, options)
Easy way to do a get requests with some query arguments, it returns a ">Deferrer
object.
<%= view.component("code", {code: "requests/get.js", run: true}) %>
">post hr.Requests.post(url, arguments, options)
Easy way to do a post requests with some body arguments, it returns a ">Deferrer
object.
<%= view.component("code", {code: "requests/post.js", run: true}) %>
">getJSON hr.Requests.getJSON(url, arguments, options)
Like get but use JSONP callback to do cross-domain requests.
<%= view.component("code", {code: "requests/getjson.js", run: true}) %>
The class Logger let you manage different log in a standart way with a global configuration.
">Logger.addNamespace hr.Logger.addNamespace(namespace, [handler])
Logger let you create logging object linked to a namespace and a handler.
The namespace is a string defining the context of messages to this logger.
The handler is an object with the following method : "log", "debug", "warn", "error"
<%= view.component("code", {code: "logger/namespace.js", run: true}) %>
logging is a logger instance linked to the namespace "base"
.
Log messages are visible in the console of your navigator
<%= view.component("code", {code: "logger/logging.js", run: true}) %>
Web applications often provide linkable, bookmarkable, shareable URLs for important locations in the app. Until recently, hash fragments (#page) were used to provide these permalinks, but with the arrival of the History API, it's now possible to use standard URLs (/page). hr.Router provides methods for routing client-side pages, and connecting them to actions and events.
">extend hr.Router.extend(properties, [classProperties])
Get started by creating a custom router class. Define actions that are triggered when certain URL fragments are matched, and provide a routes hash that pairs routes to actions.
<%= view.component("code", {code: "router/extend.js"}) %>
">route router.route(route, name, [callback])
Manually create a route for the router, The route
argument may be a routing string or regular expression. Each matching capture from the route or regular expression will be passed as an argument to the callback. The name
argument will be triggered as a "route:name"
event whenever the route is matched.
">navigate router.navigate(url, [mode, data, options])
Whenever you reach a point in your application that you'd like to save as a URL, call navigate in order to update the URL. If you wish to not call the route function, set the trigger option to false
.
">start router.start()
Start the router to listen to url changeemnt.
<%= view.component("code", {code: "router/route.js", run:true}) %>
The module Urls gives access to methods to simply calculate application urls.
">base hr.urls.route(*args)
Return complete url in the application using the configuration variable baseUrl
.
<%= view.component("code", {code: "urls/base.js", run: true}) %>
">static hr.Urls.static(*args)
Return complete url in the application for a content in the static directory using the configuration variable staticDirectory
.
<%= view.component("code", {code: "urls/static.js", run: true}) %>
">route hr.Urls.route(route, args, [base])
Return complete url in the application for a route and the route arguments using the configuration variable router.mode
.
<%= view.component("code", {code: "urls/route.js", run: true}) %>
">extendRules hr.Urls.extendRules(rules)
Extend urls rules of module Urls.
<%= view.component("code", {code: "urls/rules.js", run:true}) %>
Module Urls is accessible in templates using hr.urls
.
<%= view.component("code", {code: "urls/template.html"}) %>
The module I18n gives an easy way to manage different languages for your application.
">loadLocale hr.I18n.loadLocale(lang)
Load translation corresponding for the language lang.
<%= view.component("code", {code: "i18n/load.js", run: true}) %>
">translate hr.I18n.t(scope, [options])
Return the translation for scope. The translation string is parsed like a template using options.
The translations tables is something like that :
<%= view.component("code", {code: "i18n/en.json"}) %>
Module i18n is accessible directly in the template using hr.I18n
.
<%= view.component("code", {code: "i18n/template.html"}) %>
The module Storage is a simple interface to the navigator localStorage
.
It supports storage of complex object and not just string.
For gestion of cached values or resources, use instead module ">Cache.
">get hr.Storage.get(key)
Return a value stored in the localStorage
.
">set hr.Storage.set(key, value)
Define a value to store in the localStorage
.
">remove hr.Storage.remove(key)
Remove a value from localStorage
.
">clear hr.Storage.clear()
Clear all the values stored in the localStorage
.
">usedSpace hr.Storage.usedSpace()
Return the number of value stored in localStorage
.
The module Cache use the module ">Storage to storage content for a timelapse.
The good way to use caching is to use ">Cache.namespace.
Cache can store any json-serializable value to any json-serializable key. Cache stored key is relative to the application build revision.
<%= view.component("code", {code: "cache/key.js"}) %>
">get hr.Cache.get(namespace, key)
Return a value stored in the Storage
.
">set hr.Cache.set(namespace, key, value, expiration)
Define a value to store in the Storage
for a timelapse.
expiration define the timelapse in seconds, if expiration <‹ 0 then value don't have a timelife.
">remove hr.Cache.remove(namespace, key)
Remove a value from Storage
.
">clear hr.Cache.clear()
Clear the all cache without clearing the all Storage
.
">namespace hr.Cache.namespace(namespace)
Return a cache interface centered arround a namespace.
<%= view.component("code", {code: "cache/namespace.js", run: true}) %>
The module Resources let you load resources (such as templates, ...) in a standart way.
">load hr.Resources.load(namespace, ressource)
Load a ressource in a namespace and return a ">Deferred
.
<%= view.component("code", {code: "resources/load.js", run: true}) %>
">addNamespace hr.Resources.addNamespace(namespace, options)
Add a namespaces for resources with options for loading.
<%= view.component("code", {code: "resources/namespace.js", run: true}) %>
">addLoader hr.Resources.addLoader(name, handler)
Add a new loader for loading resources.
Base loader are :
- http : Load resources using http requests and caching
- require : Load resources from application source code using require
<%= view.component("code", {code: "resources/loader.js", run: true}) %>