Content

Routing

A start point for all programms is a public static void main(String[] args). Start point for web applications is a route. Sirius.js have a four types of routes. Obviously first it's common url, when hash change, it's call bound method. Example:

Obviously first it's common url, when hash change, it's call bound method. Example:

"#/:action" : {controller: MyController, action: "my_action" }

And when our url address equal #/anything will be call MyController.my_action

Other type of routes, is a 404. When in url changed, but does not have a suitable route, then will be called (if defined) 404 route.

404: function() {
  alert("404 Not Found");
}

Third types of routing is a event. Event in application it's the same as url changes.

"click #my-id" : {controller: MyController, action: "my_event_action"}

Fourth type is a custom event:

"custom:event" : {controller: MyController, action: "my_custom_event_action"}

As you can see, routing contain two parts: left and right. In left part you may define route for url changes, 404, events, and custom events. When it's a url changes, then you may define parameters inside url and they will be passed into action:

  "#/static" //call when url will be equal #/static
  "#/:title" //when url equal #/anything, and in bound action, pass parameter
  "#/:title/[0-9]+" //when url #/anything/1 then in action pass "anything" and "1", but
                   //when url is a #/anything/not-number then route ignored
  "#/a/b/*" //when is route start with #/a/b/c/d/ and in bounded method pass "c", "d", ...  

When it's a event, then must be start with eventname, and end with selector:

"click #id"
"dblclick div.my-class"
"keypress input"

Under the hood, it's only bind you selector with given eventname.

When it's a custom event, then must be contain colon :

When it's a 404 it's just 404 :)

It's all about left part.

Right part it's must be function or object. When it's a function everything is just.

But when it's object...

Object must be contain controller and action fields. These fields are required.
"#/" : { controller: MyController, action: "my_action" }

Also object might be contain a helpers: after\before\guard\data.

Before and after

You might define in object needful method that will be called, before action or after..

var MyController = {
  action: function() { },
  before_my_action: function() { }
  after_my_action: function() { }
}

//in routes

"#" : { controller: MyController, action: "action", before: "before_my_action", after: "after_my_action" }

Then the sequence of calls:

But when you named before\after function as before_X or after_X, where X is name of action, then you can not specify it's in object:

var MyController = {
  action: function() { },
  before_action: function() { }
  after_action: function() { }
}

//in routes

"#" : { controller: MyController, action: "action" }

Calls:

And finally before or after might be a simple function.

"#" : { controler: MyController, action: "action", before: function() { ... }, after: function() { ... } }

Guards

It's often necessary to limit calls only when something happened - password entry, image loaded, etc... For it you might create a guard function, for example for Enter pressed:

var MyController {
  action: function() {}
  before_action: function() {},
  after_action: function() {},
  guard_action: function(event) {
    if (event.which == 13) //Enter
      return true;
    return false;
  }
}

"keypress input" : { controller: MyController, action: "action", guard: "guard_action" }
// or automatically find guard function
"keypress input" : { controller: MyController, action: "action"}

Then when you press `Enter` then action will be called: Calls:

Otherwise only:

Data

When you click on element, with high probability we can say that you write the next code:

//jQuery
//click on element
function(event) {
  var target = $(event.target).attr("id"); //or class or data-* or ...  
}

With sirius.js you may extract data from target with:

//1
"click element" : { controller: Controller, action: "action", data: ["id", "class"] }
//or 2 
"click element" : { controller: Controller, action: "action", data: "data-id" }
Then in Controller.action
var Controller = {
  action: function(event, param1, param2) { // param1=id param2= class for 1) and param1 = data-id for 2.
    ...
  }

}

That's all you need to know about routing.

Controllers

As you can see, controllers it's only object with methods.

var MyController = {
  action0 : function() {},
  action1 : function() {},
  other_action : function() {}
}

Templates

You might use any templates for Sirius applcations. EJS, underscore.template or others.

Collections

Collections - is a wrap on javascript arrays. It's have a methods for server synchronization, methods for manipulation collection, and methods for traversal.

Constructor

Signature of constructor for collection is looked as

1
2
3
4
5
6
constructor: (klass, klasses, options = {
  every: 0
  on_add : (model) ->
  on_remove: (model) ->
  remote : () ->
})

Manipulation

For example, when we have a class Person:

1
2
3
4
5
class Person
  @attrs: ["id", "name"]
  @guid_for: "id"
  compare: (other_person) ->
    other_person.get("id") == @.get("id")

Then we might use a Sirius.Collection for storage all Person. The simple example

1
people = new Sirius.Collection(Person)
And then we might add or remove person from Collection:
1
2
3
4
5
6
people.add(new Person({name : "Joe"})
ann = new Person({name: "Ann"})
people.push(ann) # push alias for add
people.size() # => 2
people.remove(mike)
people.size() # => 1

We manipulate with collection firstly we add a new Person into collection, and after add yet another person, and end we remove one person from collection. Simple.

Synchronization

For synchronization you might call collection.sync(every) method, after it, start synchronized with server. For stop synchronization, call collection.unsync().

Traversal

For traverse a collection, use standard for other languages methods like a find, find_all, filter and others, see in documentation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
people.find("name", "Joe") # => model or null
people.find("name", "Joe") # => array of models

people.each((model) -> ...)

people.filter((model) -> model.name == "Ann") # => [ models... ]

people.index(model) # => numeric or null

people.size() # => actual size of collection

people.all() # => [ all models ... ]

Models

With Sirius you might use any model library, or built in.

Example:

class Person
  @attrs        : ["id", {"name" : "Nobody"}, "age"]            // 1
  @guid_for     : "id"                                          // 2
  @form_name    : "form-name"                                   // 3

  @has_many     : ["group"]                                     // 4
  @has_one      : ["city"]                                      // 5

  @validate:                                                    // 6
    name :
      format   : with: /^[A-Z].+/
      presence : true
      length   : min: 3
    age:
      numericality: only_integers: true
      validate_with: (value) ->
        #...validate
        true

  @to:                                                          // 7
    id: tag: "b", class: "model-guid"

  to_string: () ->                                              // 8
    "My name is #{@.get('name'}"


class Group extends BaseModel
  @attrs: ["name", "person_id"]
  @belongs_to: [{model: "person", back: "id"}]                 // 9
  @to:
    name: tag: "span"


person = new Person({age: "abc"})                              // 10
person.get("age")   # => "abc"
person.get("name") # => Nobody
person.valid() # => false
person.save()    # => false, not valid age
person.set("age", 25)
person.save() # => true
person.set("name", "qwerty")
person.save(true) # => exception!
person.add_group(new Gropup())
person.add_city(new City()) # => ok
person.add_city(new City()) # => exception, already exist

person.to_html() # => <b class='model-guid'>-guid-</b>...

Predefined Validators

Default validators and parameters

to_html

Convert model into array of element instances

var m = new MyModel({"id": 10, "title": "my title", "description": "text..."});
m.to_html() // => "<b class = 'my-model-id'>10</b><span class = 'my-model-title'>my title</span><div>text...</div>"
   person.to_html() // =>
    "<b class='person-id'>1</b>
     <span class='person-name'>Abc</span>
       <p class = 'group'>
         <span>group-0</span>
         <div>1</div>
         <span>group-1</span>
         <div>1</div>
       </p>

from_html

Generate a new model instance from form
//html form
     <form name="my_model" id="my-model-form">
       <input type="hidden" name="id" class="my-id" value="1" />
       <input type="text" name="title" class="title" value="new title" />
       <textarea name="description">text...</textarea>
     </form>

     var m = MyModel.from_html("my-model-form");
     m.get("id")          // => 1
     m.get("title")       // => new title
     m.get("description") // => text...

to_json

Convert model instance in json

var m = new MyModel({"id": 10, "description", "text"});
m.to_json() // => {"id":10,"title":"default title","description":"text"}
m.to_json(true) // => {"my_model":{"id":10,"title":"default title","description":"text"}}

person  = new Person({id: 1, name: "Abc"})
group0  = new Group({name: "group-0"})
group1  = new Group({name: "group-1"})

person.add_group(group0)
person.add_group(group1)

person.to_json() // =>
// {"id":1,
//  "name":"Abc",
//   "group":[
//     {"name":"group-0","person_id":1},
//     {"name":"group-1","person_id":1}
//   ]
// }

from_json

Create a new model instance from json structure.
var json = JSON.stringify({"id": 10, "description": "text"});
var m = MyModel.from_json(json);
m.get("id") // => 10
m.get("description") // => "text"
m.get("title") // => "default title"

var json = JSON.stringify({"id":1,"group":[{"name":"group-0","person_id":1},{"name":"group-1","person_id":1}]})
var person = Person.from_json(json, {group: Group});
person.get('group') // => [Group, Group]

var person0 = Person.from_json(json)
person.get('group') // => [{name: 'group-0', ... }, {name: 'group-1', ...}]

save

Redefine it's method in you model, and call super when want to check model, or when synchronize with server, or specify you action...

class Person extends BaseModel
  # ....

  save: () -> #withou exceptions
    if super
      # ajax synchronize
    else
      false

valid

Return true, when current model instance is valid.

compare

For usage with collection, you need redefine this method in own collection, it's used for comparing models.
1
2
3
4
5
class Person
  @attrs: ["id"]
  @guid_for : "id"
  compare: (other) ->
    @.get("id") == other.get("id")

after_create

Callback function, will be called after model creating.
1
2
3
4
5
6
class Person
  @attrs: ["name"]
  after_create() ->
    alert "Hi! I'm #{@.get("name"}"

new Person({"name": "Sirius"}) # => alert Hi! I'm Sirius

Adapters

By default, sirius had a two adapters for jQuery and for prototypejs. You might create own.

Adapter must define next methods:

class YouAdapter
  bind: (selector, event, fn) ->                        # 1
  form_to_json: (selector) ->                           # 2
  fire: (element, event, params...) ->                  # 3
  get_property: (event, properties...) ->               # 4 

Methods:

For example, JQueryAdapter

class JQueryAdapter extends Adapter
  bind: (selector, event, fn) ->
    jQuery(document).on(event, selector, fn)

  form_to_json: (selector) ->
    o = {}
    a = jQuery(selector).serializeArray()

    for obj in a
      do(obj) =>
        name = obj["name"]
        value = obj["value"]
        if o[name]
          if !o[name].push
            o[name] = [o[name]]
          else
            o[name].push(value || '')
        else
          o[name] = value || ''
    JSON.stringify(o)

  fire: (element, event, params...) ->
    jQuery(element).trigger(event, params)

  get_property: (event, properties) ->
    for p in properties then jQuery(event.target).attr(p)

Sirius.Application

Call method run with options:
Controller = 
  ....

routes = 
  ....

SirisusApplication.run
  route  : routes

Options:

  • 1. route [Object] a routing object
  • 2. logger [Function] you own logger function, by default it's only console.log
  • 3. log [Boolean] - when true, all messages pass into logger
  • 4. adapter[Adapter] - adapter instance
  • 5. start - start url

Redirect Helper

Redirect with Sirius.redirect function, for example in controller:

Controller = 
  action: () ->
    if ...
      Sirius.redirect("#/")

Events

When Sirius application start, then generate application:run event.

Next event it's a application:hashchange, when change url hash.

Other event it's a application:404 when route not matched.

For all this events, you might define routes:

"application:start" : function() { alert("go!"); },
"application:hashchange" : function(current, prev) { alert("change"); }
"application:404" : function(current, prev) { alert("404"); }