knockout-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +12 -0
  3. data/Gemfile.lock +134 -0
  4. data/HISTORY.md +12 -0
  5. data/README.md +106 -0
  6. data/Rakefile +37 -0
  7. data/knockout-rails.gemspec +27 -0
  8. data/lib/assets/javascripts/knockout/bindings.js.coffee +1 -0
  9. data/lib/assets/javascripts/knockout/model.js.coffee +112 -0
  10. data/lib/assets/javascripts/knockout/observables.js.coffee +1 -0
  11. data/lib/knockout-rails.rb +5 -0
  12. data/lib/knockout-rails/engine.rb +6 -0
  13. data/lib/knockout-rails/version.rb +3 -0
  14. data/lib/tasks/knockout-rails_tasks.rake +4 -0
  15. data/spec/dummy/Rakefile +7 -0
  16. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  17. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  18. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  19. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  20. data/spec/dummy/app/mailers/.gitkeep +0 -0
  21. data/spec/dummy/app/models/.gitkeep +0 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  23. data/spec/dummy/config.ru +4 -0
  24. data/spec/dummy/config/application.rb +53 -0
  25. data/spec/dummy/config/boot.rb +10 -0
  26. data/spec/dummy/config/database.yml +25 -0
  27. data/spec/dummy/config/environment.rb +5 -0
  28. data/spec/dummy/config/environments/development.rb +30 -0
  29. data/spec/dummy/config/environments/production.rb +60 -0
  30. data/spec/dummy/config/environments/test.rb +39 -0
  31. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/spec/dummy/config/initializers/inflections.rb +10 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  35. data/spec/dummy/config/initializers/session_store.rb +8 -0
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  37. data/spec/dummy/config/locales/en.yml +5 -0
  38. data/spec/dummy/config/routes.rb +58 -0
  39. data/spec/dummy/lib/assets/.gitkeep +0 -0
  40. data/spec/dummy/public/404.html +26 -0
  41. data/spec/dummy/public/422.html +26 -0
  42. data/spec/dummy/public/500.html +26 -0
  43. data/spec/dummy/public/favicon.ico +0 -0
  44. data/spec/dummy/script/rails +6 -0
  45. data/spec/javascripts/knockout/bindings_spec.js.coffee +2 -0
  46. data/spec/javascripts/knockout/model_spec.js.coffee +85 -0
  47. data/spec/javascripts/knockout/observables_spec.js.coffee +3 -0
  48. data/spec/javascripts/spec.css +3 -0
  49. data/spec/javascripts/spec.js.coffee +3 -0
  50. data/spec/javascripts/support/mock-ajax.js +207 -0
  51. data/spec/knockout_spec.rb +14 -0
  52. data/spec/spec_helper.rb +10 -0
  53. data/spec/support/global.rb +7 -0
  54. data/spec/support/matchers.rb +36 -0
  55. data/vendor/assets/javascripts/knockout.js +7 -0
  56. data/vendor/assets/javascripts/knockout/knockout.js +3153 -0
  57. data/vendor/assets/javascripts/knockout/knockout.mapping.js +676 -0
  58. data/vendor/assets/javascripts/knockout/sugar-1.1.1.js +5828 -0
  59. metadata +206 -0
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ *.swp
5
+ spec/dummy/db/*.sqlite3
6
+ spec/dummy/log/*.log
7
+ spec/dummy/tmp/
8
+ spec/dummy/public/assets/
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'coffee-script'
6
+ group :development, :test do
7
+ gem 'pry'
8
+
9
+ gem 'jasminerice'
10
+ end
11
+
12
+ gem 'jquery-rails' # For the dummy app
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