Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
KnockoutJS for Rails with Sweetness
CoffeeScript Ruby JavaScript
Tree: f22a61fe21

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
lib
spec
vendor/assets/javascripts/knockout
.gitignore
Gemfile
HISTORY.md
README.md
Rakefile
knockout-rails.gemspec

README.md

Knockout - easily use Knockout.js from the Rails app

If you have any questions please contact me @dnagir.

This provides a set of conveniences for you to use more like Backbone or Spine, but still fully leveraging KnockoutJS.

Install

Add it to your Rails application's Gemfile:

gem 'knockout-rails'

Then bundle install.

Reference knockout from your JavaScript as you normally do with Rails 3.1 Assets Pipeline.

Usage

Model

After you've referenced the knockout you can create your first persistent Model.

class @Page extends ko.Model
  @persistAt 'page' # This is enough to save the model RESTfully to `/pages/{id}` URL
  @fields ['id', 'name', 'whatever'] # This is optional and will be inferred if not used

Too simple. This model conforms to the response of inherited_resources Gem.

Now you can create the model in your HTML. Note that we don't do a roundtrip to fetch the data as we already have it when rendering the view.

= content_for :script do
  :javascript
    jQuery(function(){
      // Create the viewModel with prefilled data
      window.page = new Page(#{@page.to_json});
      ko.applyBindings(window.page); // And bind everything
    });

Of course you can manipulate the object as you wish:

page.name 'Updated page'
page.save() # saves it to the server using PUT: /pages/123
page.name '' # Assign an invalid value that is validated on the server
request = page.save() # returns the jQuery Deferred, so you can chain into it when necessary
request.always (xhr, status) ->
  # The response is 422 with JSON: {name: ["invalid name", "should not be blank"]}
  # And now we have the errors set automatically!
  page.errors.name() # "invalid name, should not be blank"
  # even more than that, errors are already bound and shown in the HTML (see the view below)

Now let's see how we can show the validation errors on the page and bind everything together.

%form.page.formtastic{:data => {:bind =>'submit: save'}}
  %fieldset
    %ol
      %li.input.string
        %label.label{:for=>:page_name} Name
        %input#page_name{:type=>:text, :data=>{:bind=>'value: name'}}
        %span.inline-error{:data=>{:bind=>'visible: errors.name, text: errors.name'}}

Model Validations

If you are using the model, you can also take advantage of the client-side validation framework.

The piece of code below should explain everything, including some of the options.

class @Page extends ko.Model
  @persistAt 'page'

  validates: (page) ->
    acceptance  'agree_to_terms' # Value is truthy
    presence    'name', 'body' # Non-empty, non-blank stringish value
    email       'author' # Valid email, blanks allowed

    presence      'password'
    confirmation  'passwordConfirmation', {:confirms => 'password'} # Blanks allowed

    # numericality:
    numericality  'rating'
    numericality  'rating', {min: 1, max: 5}

    # Inclusion/exclusion
    inclusion   'subdomain', ["mine", "yours"]
    exclusion   'subdomain', ["www", "www2"] 

    format      'code', /\d+/ # Regex validation, blanks allowed
    length      'name', {min: 3, max: 10} # Stringish value should be with the range

    # Custom message
    presence    'name', {message: 'give me a name, yo!'}

    # Conditional validation - use the `page` model passed in as argument
    presence    'name' unless page.id?

    # Custom inline validation
    custom -> page.errors.name("should be funky") if page.name().indexOf('funky') < 0

It is recommended to avoid custom inline validations and create your own validators instead:

ko.validators.funky = (model, fields, options) ->
  # options - is an optional set of options passed to the validator
  word = options.word || 'funky'
  result = {}
  fields.each (field) ->
    result[field] = "should be #{word}" if model[field]().indexOf(word) < 0

  # you MUST return a hash of field-error or empty hash/null for no errors
  return result 

so that you can use it like so:

validates: (page) ->
  funky 'name', {word: 'yakk'}

Here's how you would check whether the model is valid or not (assuming presence validation on name field):

page = new @Page name: ''
page.isValid() # false

page.name = 'Home'
page.isValid() # true

page.performValidation() # To force the validation, you don't need to do that really

Every validator has its own set of options.

The general format of the validator is: validatorName field1, field2, field3, {optional: options, asA: hash}. But it is really up to the falidator how to treat those.

Model Events

class @Page extends ko.Model
  @persistAt 'page'

  # Subscribe to 'sayHi' event
  @upon 'sayHi', (name) ->
    alert name + @name

page = Page.new name: 'Home'
page.trigger 'sayHi', 'Hi '
# will show "Hi Home"

Model Callbacks

The callbacks are just convenience wrappers over the predefined events. Some of them are:

class @Page extends ko.Model
  @persistAt 'page'

  @beforeSave ->
    @age = @birthdate - new Date()

# This would be similar to

class @Page extends ko.Model
  @persistAt 'page'

  @on 'beforeSave', ->
    @age = @birthdate - new Date()

Bindings

This gem also includes useful bindings that you may find useful in your application. For example, you can use autosave binding by requiring knockout/bindings/autosave.

Or if you want to include all of the bindings available, then require knockout/bindings/all.

The list of currently available bindings:

  • autosave - automatically persists the model whenever any of its attributes change. Apply it to a form element. Examples: autosave: page, autosave: {model: page, when: page.isEnabled, unless: viewModel.doNotSave }.
  • inplace - converts the input elements into inplace editing with 'Edit'/'Done' buttons. Apply it on input elements similarly to the value binding.
  • color - converts an element into a color picker. Apply it to a div element: color: page.fontColor. Depends on pakunok gem (specifically - its colorpicker asset).
  • onoff - Converts checkboxes into iOS on/off buttons. Example: onoff: page.isPublic. It depends on ios-chechboxes gem.
  • animate - runs the animation when dependent attributes change. Example: animate: {width: quotaUsed, height: quotaUsed(), duration: 2000}.

Please see the specs for more detailed instruction on how to use the specific binding.

Development

Help

  • Source hosted at GitHub
  • Report issues and feature requests to GitHub Issues
  • Ping me on Twitter @dnagir
  • Look at the HISTORY.md file for current TODO list and other details.

Setup

Assuming you already cloned the repo in cd-d into it:

bundle install
# Now run the Ruby specs
bundle exec rspec spec/
# Now start JavaScript server for specs:
cd spec/dummy
bundle exec rails s
# go to http://localhost:3000/jasmine to see the results

Now you can go to spec/javascripts and start writing your specs and then modify stuff in lib/assets/javascripts to pass those.

Pull requests are very welcome, but please include the specs! It's extremely easy to write those!

License

MIT

Something went wrong with that request. Please try again.