knockout-rails 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +134 -0
- data/HISTORY.md +12 -0
- data/README.md +106 -0
- data/Rakefile +37 -0
- data/knockout-rails.gemspec +27 -0
- data/lib/assets/javascripts/knockout/bindings.js.coffee +1 -0
- data/lib/assets/javascripts/knockout/model.js.coffee +112 -0
- data/lib/assets/javascripts/knockout/observables.js.coffee +1 -0
- data/lib/knockout-rails.rb +5 -0
- data/lib/knockout-rails/engine.rb +6 -0
- data/lib/knockout-rails/version.rb +3 -0
- data/lib/tasks/knockout-rails_tasks.rake +4 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/stylesheets/application.css +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +53 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +60 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/javascripts/knockout/bindings_spec.js.coffee +2 -0
- data/spec/javascripts/knockout/model_spec.js.coffee +85 -0
- data/spec/javascripts/knockout/observables_spec.js.coffee +3 -0
- data/spec/javascripts/spec.css +3 -0
- data/spec/javascripts/spec.js.coffee +3 -0
- data/spec/javascripts/support/mock-ajax.js +207 -0
- data/spec/knockout_spec.rb +14 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/global.rb +7 -0
- data/spec/support/matchers.rb +36 -0
- data/vendor/assets/javascripts/knockout.js +7 -0
- data/vendor/assets/javascripts/knockout/knockout.js +3153 -0
- data/vendor/assets/javascripts/knockout/knockout.mapping.js +676 -0
- data/vendor/assets/javascripts/knockout/sugar-1.1.1.js +5828 -0
- metadata +206 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
knockout-rails (0.0.1)
|
5
|
+
execjs
|
6
|
+
jquery-rails
|
7
|
+
sprockets (>= 2.0.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionmailer (3.1.1)
|
13
|
+
actionpack (= 3.1.1)
|
14
|
+
mail (~> 2.3.0)
|
15
|
+
actionpack (3.1.1)
|
16
|
+
activemodel (= 3.1.1)
|
17
|
+
activesupport (= 3.1.1)
|
18
|
+
builder (~> 3.0.0)
|
19
|
+
erubis (~> 2.7.0)
|
20
|
+
i18n (~> 0.6)
|
21
|
+
rack (~> 1.3.2)
|
22
|
+
rack-cache (~> 1.1)
|
23
|
+
rack-mount (~> 0.8.2)
|
24
|
+
rack-test (~> 0.6.1)
|
25
|
+
sprockets (~> 2.0.2)
|
26
|
+
activemodel (3.1.1)
|
27
|
+
activesupport (= 3.1.1)
|
28
|
+
builder (~> 3.0.0)
|
29
|
+
i18n (~> 0.6)
|
30
|
+
activerecord (3.1.1)
|
31
|
+
activemodel (= 3.1.1)
|
32
|
+
activesupport (= 3.1.1)
|
33
|
+
arel (~> 2.2.1)
|
34
|
+
tzinfo (~> 0.3.29)
|
35
|
+
activeresource (3.1.1)
|
36
|
+
activemodel (= 3.1.1)
|
37
|
+
activesupport (= 3.1.1)
|
38
|
+
activesupport (3.1.1)
|
39
|
+
multi_json (~> 1.0)
|
40
|
+
arel (2.2.1)
|
41
|
+
builder (3.0.0)
|
42
|
+
coderay (0.9.8)
|
43
|
+
coffee-script (2.2.0)
|
44
|
+
coffee-script-source
|
45
|
+
execjs
|
46
|
+
coffee-script-source (1.1.3)
|
47
|
+
diff-lcs (1.1.3)
|
48
|
+
erubis (2.7.0)
|
49
|
+
execjs (1.2.9)
|
50
|
+
multi_json (~> 1.0)
|
51
|
+
haml (3.1.3)
|
52
|
+
hike (1.2.1)
|
53
|
+
i18n (0.6.0)
|
54
|
+
jasminerice (0.0.8)
|
55
|
+
haml
|
56
|
+
jquery-rails (1.0.16)
|
57
|
+
railties (~> 3.0)
|
58
|
+
thor (~> 0.14)
|
59
|
+
json (1.6.1)
|
60
|
+
mail (2.3.0)
|
61
|
+
i18n (>= 0.4.0)
|
62
|
+
mime-types (~> 1.16)
|
63
|
+
treetop (~> 1.4.8)
|
64
|
+
method_source (0.6.7)
|
65
|
+
ruby_parser (>= 2.3.1)
|
66
|
+
mime-types (1.17.2)
|
67
|
+
multi_json (1.0.3)
|
68
|
+
polyglot (0.3.3)
|
69
|
+
pry (0.9.7.4)
|
70
|
+
coderay (~> 0.9.8)
|
71
|
+
method_source (~> 0.6.7)
|
72
|
+
ruby_parser (>= 2.3.1)
|
73
|
+
slop (~> 2.1.0)
|
74
|
+
rack (1.3.5)
|
75
|
+
rack-cache (1.1)
|
76
|
+
rack (>= 0.4)
|
77
|
+
rack-mount (0.8.3)
|
78
|
+
rack (>= 1.0.0)
|
79
|
+
rack-ssl (1.3.2)
|
80
|
+
rack
|
81
|
+
rack-test (0.6.1)
|
82
|
+
rack (>= 1.0)
|
83
|
+
rails (3.1.1)
|
84
|
+
actionmailer (= 3.1.1)
|
85
|
+
actionpack (= 3.1.1)
|
86
|
+
activerecord (= 3.1.1)
|
87
|
+
activeresource (= 3.1.1)
|
88
|
+
activesupport (= 3.1.1)
|
89
|
+
bundler (~> 1.0)
|
90
|
+
railties (= 3.1.1)
|
91
|
+
railties (3.1.1)
|
92
|
+
actionpack (= 3.1.1)
|
93
|
+
activesupport (= 3.1.1)
|
94
|
+
rack-ssl (~> 1.3.2)
|
95
|
+
rake (>= 0.8.7)
|
96
|
+
rdoc (~> 3.4)
|
97
|
+
thor (~> 0.14.6)
|
98
|
+
rake (0.9.2.2)
|
99
|
+
rdoc (3.11)
|
100
|
+
json (~> 1.4)
|
101
|
+
rspec (2.7.0)
|
102
|
+
rspec-core (~> 2.7.0)
|
103
|
+
rspec-expectations (~> 2.7.0)
|
104
|
+
rspec-mocks (~> 2.7.0)
|
105
|
+
rspec-core (2.7.1)
|
106
|
+
rspec-expectations (2.7.0)
|
107
|
+
diff-lcs (~> 1.1.2)
|
108
|
+
rspec-mocks (2.7.0)
|
109
|
+
ruby_parser (2.3.1)
|
110
|
+
sexp_processor (~> 3.0)
|
111
|
+
sexp_processor (3.0.8)
|
112
|
+
slop (2.1.0)
|
113
|
+
sprockets (2.0.3)
|
114
|
+
hike (~> 1.2)
|
115
|
+
rack (~> 1.0)
|
116
|
+
tilt (~> 1.1, != 1.3.0)
|
117
|
+
thor (0.14.6)
|
118
|
+
tilt (1.3.3)
|
119
|
+
treetop (1.4.10)
|
120
|
+
polyglot
|
121
|
+
polyglot (>= 0.3.1)
|
122
|
+
tzinfo (0.3.31)
|
123
|
+
|
124
|
+
PLATFORMS
|
125
|
+
ruby
|
126
|
+
|
127
|
+
DEPENDENCIES
|
128
|
+
coffee-script
|
129
|
+
jasminerice
|
130
|
+
jquery-rails
|
131
|
+
knockout-rails!
|
132
|
+
pry
|
133
|
+
rails (>= 3.1.1)
|
134
|
+
rspec
|
data/HISTORY.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# 0.0.2 - in progress
|
2
|
+
|
3
|
+
- Support collections (fetch multiple records)
|
4
|
+
- Client side validation
|
5
|
+
|
6
|
+
# 0.0.1 - 17 November 2011
|
7
|
+
Initial release. Bare bones moved over from other project. Includes:
|
8
|
+
|
9
|
+
- persist view models RESTfully
|
10
|
+
- compliant with Rails inherited_resources response
|
11
|
+
- built in, bindable server side validation support (errors accessible vie `model.errors.attribute_name()`)
|
12
|
+
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Knockout - easily use Knockout.js from the Rails app
|
2
|
+
|
3
|
+
If you have any questions please contact me [@dnagir](http://www.ApproachE.com).
|
4
|
+
|
5
|
+
This provides a set of conveniences for you to use more like Backbone or Spine, but still fully leveraging KnockoutJS.
|
6
|
+
|
7
|
+
# Install
|
8
|
+
|
9
|
+
Add it to your Rails application's `Gemfile`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'knockout'
|
13
|
+
```
|
14
|
+
|
15
|
+
Then `bundle install`.
|
16
|
+
|
17
|
+
Reference `knockout` from your JavaScript as you normally do.
|
18
|
+
|
19
|
+
|
20
|
+
# Usage
|
21
|
+
|
22
|
+
## Model
|
23
|
+
|
24
|
+
After you've referenced the `knockout` you can create your first persistent Model.
|
25
|
+
|
26
|
+
```coffee
|
27
|
+
class Page extends ko.Model
|
28
|
+
@configure 'page' # This is enough to save the model RESTfully to `/pages/{id}` URL
|
29
|
+
```
|
30
|
+
|
31
|
+
Too simple. This model conforms to the response of [inherited_resources](https://github.com/josevalim/inherited_resources) Gem.
|
32
|
+
|
33
|
+
|
34
|
+
Now you can create the model in your HTML.
|
35
|
+
*Note* that we don't do a roundtrip to fetch the data as we already have it when rendering the view.
|
36
|
+
|
37
|
+
```haml
|
38
|
+
= content_for :script do
|
39
|
+
:javascript
|
40
|
+
jQuery(function(){
|
41
|
+
// Create the viewModel with prefilled data
|
42
|
+
window.page = new Page(#{@page.to_json});
|
43
|
+
ko.applyBindings(window.page); // And bind everything
|
44
|
+
});
|
45
|
+
```
|
46
|
+
|
47
|
+
Of course you can manipulate the object as you wish:
|
48
|
+
|
49
|
+
```coffee
|
50
|
+
page.name 'Updated page'
|
51
|
+
page.save() # saves it to the server using PUT: /pages/123
|
52
|
+
page.name '' # Assign an invalid value that is validated on the server
|
53
|
+
request = page.save() # returns the jQuery Deferred, so you can chain into it when necessary
|
54
|
+
request.always (xhr, status) ->
|
55
|
+
# The response is 422 with JSON: {name: ["invalid name", "should not be blank"]}
|
56
|
+
# And now we have the errors set automatically!
|
57
|
+
page.errors.name() # "invalid name, should not be blank"
|
58
|
+
# even more than that, errors are already bound and shown in the HTML (see the view below)
|
59
|
+
```
|
60
|
+
|
61
|
+
Now let's see how we can show the validation errors on the page and bind everything together.
|
62
|
+
|
63
|
+
```haml
|
64
|
+
|
65
|
+
%form.page.formtastic{:data => {:bind =>'submit: save'}}
|
66
|
+
%fieldset
|
67
|
+
%ol
|
68
|
+
%li.input.string
|
69
|
+
%label.label{:for=>:page_name} Name
|
70
|
+
%input#page_name{:type=>:text, :data=>{:bind=>'value: name'}}
|
71
|
+
%span.inline-error{:data=>{:bind=>'visible: errors.name, text: errors.name'}}
|
72
|
+
```
|
73
|
+
|
74
|
+
|
75
|
+
# Development
|
76
|
+
|
77
|
+
## Help
|
78
|
+
|
79
|
+
- Source hosted at [GitHub](https://github.com/dnagir/knockout-rails)
|
80
|
+
- Report issues and feature requests to [GitHub Issues](https://github.com/dnagir/knockout-rails/issues)
|
81
|
+
- Ping me on Twitter for quickly thing [@dnagir](https://twitter.com/#!/dnagir)
|
82
|
+
- Look at the `HISTORY.md` file for current TODO list and other details.
|
83
|
+
|
84
|
+
|
85
|
+
## Setup
|
86
|
+
|
87
|
+
Assuming you already cloned the repo in cd-d into it:
|
88
|
+
|
89
|
+
```bash
|
90
|
+
bundle install
|
91
|
+
# Now run the Ruby specs
|
92
|
+
bundle exec rspec spec/
|
93
|
+
# Now start JavaScript server for specs:
|
94
|
+
cd spec/dummy
|
95
|
+
bundle exec rails s
|
96
|
+
# go to http://localhost:3000/jasmine to see the results
|
97
|
+
```
|
98
|
+
|
99
|
+
Now you can go to `spec/javascripts` and start writing your specs and then modify stuff in `lib/assets/javascripts` to pass those.
|
100
|
+
|
101
|
+
|
102
|
+
Pull requests are very welcome, but please include the specs! It's extremely easy to write those!
|
103
|
+
|
104
|
+
# License
|
105
|
+
|
106
|
+
[MIT] (http://www.opensource.org/licenses/mit-license.php)
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'KnockoutRails'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task :default => :test
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "knockout-rails/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "knockout-rails"
|
7
|
+
s.version = KnockoutRails::VERSION
|
8
|
+
s.authors = ["Dmytrii Nagirniak"]
|
9
|
+
s.email = ["dnagir@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/dnagir/knockout-rails"
|
11
|
+
s.summary = %q{Knockout.JS library for Rails Assets Pipeline with convenient Backbone/Spine-like Rails extensions.}
|
12
|
+
s.description = %q{Include the knockout.js and some of its extensions so you can pick what you need. Adds the support for models and interation with the Rails backend.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "knockout-rails"
|
15
|
+
|
16
|
+
s.add_dependency 'sprockets', '>= 2.0.0'
|
17
|
+
s.add_dependency 'execjs'
|
18
|
+
s.add_dependency 'jquery-rails'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rspec'
|
21
|
+
s.add_development_dependency 'rails', '>= 3.1.1'
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#TODO: Add additional bindings
|
@@ -0,0 +1,112 @@
|
|
1
|
+
|
2
|
+
# Module is taken from Spine.js
|
3
|
+
moduleKeywords = ['included', 'extended']
|
4
|
+
class Module
|
5
|
+
@include: (obj) ->
|
6
|
+
throw('include(obj) requires obj') unless obj
|
7
|
+
for key, value of obj when key not in moduleKeywords
|
8
|
+
@::[key] = value
|
9
|
+
obj.included?.apply(@)
|
10
|
+
@
|
11
|
+
|
12
|
+
@extend: (obj) ->
|
13
|
+
throw('extend(obj) requires obj') unless obj
|
14
|
+
for key, value of obj when key not in moduleKeywords
|
15
|
+
@[key] = value
|
16
|
+
obj.extended?.apply(@)
|
17
|
+
@
|
18
|
+
|
19
|
+
|
20
|
+
Ajax =
|
21
|
+
ClassMethods:
|
22
|
+
configure: (@className) ->
|
23
|
+
@getUrl ||= (model) ->
|
24
|
+
return model.getUrl(model) if model and model.getUrl
|
25
|
+
collectionUrl = "/#{className.toLowerCase()}s"
|
26
|
+
collectionUrl += "/#{model.id()}" if model?.id()
|
27
|
+
collectionUrl
|
28
|
+
extended: -> @include Ajax.InstanceMethods
|
29
|
+
|
30
|
+
|
31
|
+
InstanceMethods:
|
32
|
+
ignore: -> []
|
33
|
+
mapping: ->
|
34
|
+
return @__ko_mapping__ if @__ko_mapping__
|
35
|
+
mappable =
|
36
|
+
ignore: @ignore()
|
37
|
+
for k, v of this
|
38
|
+
mappable.ignore.push k unless ko.isObservable(v)
|
39
|
+
@__ko_mapping__ = mappable
|
40
|
+
|
41
|
+
toJSON: -> ko.mapping.toJS @, @mapping()
|
42
|
+
|
43
|
+
save: ->
|
44
|
+
data = {}
|
45
|
+
data[@constructor.className] =@toJSON()
|
46
|
+
params =
|
47
|
+
type: if @persisted() then 'PUT' else 'POST'
|
48
|
+
dataType: 'json'
|
49
|
+
beforeSend: (xhr)->
|
50
|
+
token = $('meta[name="csrf-token"]').attr('content')
|
51
|
+
xhr.setRequestHeader('X-CSRF-Token', token) if token
|
52
|
+
url: @constructor.getUrl(@)
|
53
|
+
contentType: 'application/json'
|
54
|
+
context: this
|
55
|
+
processData: false # jQuery tries to serialize to much, including constructor data
|
56
|
+
data: JSON.stringify data
|
57
|
+
statusCode:
|
58
|
+
422: (xhr, status, errorThrown)->
|
59
|
+
errorData = JSON.parse xhr.responseText
|
60
|
+
console.debug("Validation error: ", errorData) if console?.debug?
|
61
|
+
@updateErrors(errorData)
|
62
|
+
|
63
|
+
$.ajax(params)
|
64
|
+
#.fail (xhr, status, errorThrown)-> console.error "fail: ", this
|
65
|
+
.done (resp, status, xhr)-> @updateErrors {}
|
66
|
+
#.always (xhr, status) -> console.info "always: ", this
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
class Model extends Module
|
71
|
+
@extend Ajax.ClassMethods
|
72
|
+
|
73
|
+
constructor: (json) ->
|
74
|
+
me = this
|
75
|
+
@set json
|
76
|
+
@id ||= ko.observable()
|
77
|
+
@mapping().ignore.exclude('constructor').filter (v)->
|
78
|
+
not v.startsWith('_') and Object.isFunction me[v]
|
79
|
+
.forEach (fn) ->
|
80
|
+
original = me[fn]
|
81
|
+
me[fn] = original.bind me
|
82
|
+
me._originals ||= {}
|
83
|
+
me._originals[fn] = original
|
84
|
+
|
85
|
+
@persisted = ko.dependentObservable -> !!me.id()
|
86
|
+
|
87
|
+
proxy: -> @mapping().ignore
|
88
|
+
|
89
|
+
set: (json) ->
|
90
|
+
ko.mapping.fromJS json, @mapping(), @
|
91
|
+
me = this
|
92
|
+
@errors ||= {}
|
93
|
+
ignores = @mapping().ignore
|
94
|
+
for key, value of this
|
95
|
+
@errors[key] ||= ko.observable() unless ignores.indexOf(key) >= 0
|
96
|
+
@
|
97
|
+
|
98
|
+
updateErrors: (errorData) ->
|
99
|
+
for key, setter of @errors
|
100
|
+
field = @errors[key]
|
101
|
+
error = errorData[key]
|
102
|
+
message = if error and error.join
|
103
|
+
error.join(", ")
|
104
|
+
else
|
105
|
+
error
|
106
|
+
setter( message ) if field
|
107
|
+
@
|
108
|
+
|
109
|
+
|
110
|
+
# Export it all:
|
111
|
+
ko.Module = Module
|
112
|
+
ko.Model = Model
|