polished-knockout 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c60a918d9afb8c42b4b3727eaf288a89b5d834a8
4
+ data.tar.gz: e44e07b46ab356a82f54ced938557afa1ac6c9c8
5
+ SHA512:
6
+ metadata.gz: d8ba6d4b830ded0cd7a9b8cc49da49abba3cd51f2a1410d58bde58b3b5f8b4daf39d5929891585d6b07d077c664a779c9e74fa289a829b1dc7afca530d639ef2
7
+ data.tar.gz: d8369009288c302bba97b9769e23910f63698873cc1b5f7a125663c49656794b9d0b9a7fcb09a925daf7b37ef679390ec8c4a5d28b1831405c53f45ab2ab4459
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,15 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of overtly sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ This code of conduct applies both within project spaces and in public spaces where an individual explicitly associates their presence with the project; non-project related material on accounts explicitly marked as personal should not be considered to be so associated.
12
+
13
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
14
+
15
+ This Code of Conduct is adapted from the [Opal Code of Conduct](https://github.com/opal/opal/blob/master/CODE_OF_CONDUCT.md), which in turn is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in opal-knockout.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jared White
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Polished: Knockout
2
+
3
+ An [Opal (Ruby-to-JS)](http://opalrb.org) library for creating view models that use Knockout.js for dynamic HTML updates and event handling.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'polished-knockout'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install polished-knockout
18
+
19
+ ## Getting Started
20
+
21
+ Read the [Getting Started](http://polished-rb.github.io/knockout-rb/getting-started/) tutorial to see how easy it is to build view models and load in JSON data. Sneak peak:
22
+
23
+ ```ruby
24
+ class UserView < Knockout::ViewModel
25
+ bind_accessor :first_name, :last_name, :age, :user_types
26
+ bind_collection :favorite_foods, class_name: 'FavoriteFoodView'
27
+ end
28
+ ```
29
+
30
+ For more in-depth documentation, visit the [Documentation](http://polished-rb.github.io/knockout-rb/docs/) page.
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it ( http://github.com/polished-rb/knockout/fork )
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
39
+
40
+ ## Testing
41
+
42
+ Simply run `rackup` at your command line when you're in the project folder. It will load a webserver at port 9292. Then just go to your browser and access `http://localhost:9292`. You should get the full rspec suite runner output. (And hopefully, everything's green!)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/config.ru ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'opal-rspec'
5
+ Opal.append_path File.expand_path('../spec', __FILE__)
6
+
7
+ require 'opal/jquery'
8
+
9
+ run Opal::Server.new { |s|
10
+ s.main = 'opal/rspec/sprockets_runner'
11
+ s.append_path 'spec'
12
+ s.debug = false
13
+ s.index_path = 'spec/knockout/index.html.erb'
14
+ }
@@ -0,0 +1,14 @@
1
+ module PolishedKnockout
2
+ module KnockoutHelpers
3
+ def knockout(partial=nil, subview:nil, bind_as:nil)
4
+ if partial
5
+ render(partial: 'knockout/' + partial.to_s)
6
+ elsif subview
7
+ subview_with = bind_as || subview.split('/').last
8
+ content_tag(:div, 'data-bind' => "with: #{subview_with}") do
9
+ render(partial: 'knockout/' + subview.to_s)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ require 'polished/knockout/helpers/knockout_helpers'
2
+ module PolishedKnockout
3
+ class Railtie < Rails::Railtie
4
+ initializer "polished_knockout.knockout_helpers" do
5
+ ActiveSupport.on_load( :action_view ){ include PolishedKnockout::KnockoutHelpers }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Polished
2
+ module Knockout
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,346 @@
1
+ # Knockout::ViewModel provides a simple Opal-based Ruby wrapper around
2
+ # Knockout.js, a popular front-end data-binding and events library.
3
+ #
4
+ # Run one or more of these class methods in a Knockout::ViewModel
5
+ # subclass, aka
6
+ #
7
+ # bind_var :somevar, :anothervar
8
+ # bind_event :clickme
9
+ #
10
+ # and then wrappers will be created for the JS model that is passed to
11
+ # Knockout
12
+ #
13
+ # You can either do attr_accessor :somevar, :anothervar or write custom
14
+ # methods in your class, or you can use the shortcut bind_accessor to set
15
+ # up bind_var and attr_accessor at once.
16
+ #
17
+ # You'll need to use bind_id on a top-level view to target an element in your
18
+ # DOM with data-bind-id:
19
+ #
20
+ # bind_id :some-id
21
+ #
22
+ # Example HTML:
23
+ # <div data-bind-id="some-id">
24
+ # <span data-bind="text: somevar"> </span>, <span data-bind="html: anothervar"> </span>
25
+ # </div>
26
+ #
27
+ # Methods in your class are passed a jQuery event object, for example:
28
+ #
29
+ # <a href="#" data-bind="click: clickme">Click me!</a>
30
+ #
31
+ # def clickme(event)
32
+ # puts event.page_x
33
+ # end
34
+ #
35
+ # TODO: remove dependency on jQuery events, so for example opal-browser events
36
+ # could also be used
37
+ #
38
+ # -----
39
+ #
40
+ # Subview docs forthcoming...
41
+ #
42
+
43
+ module Knockout
44
+ class ViewModel
45
+
46
+ def self.bind_setup
47
+ if @bind_defs.nil?
48
+ @bind_defs = {
49
+ :vars => [],
50
+ :subviews => [],
51
+ :collections => [],
52
+ :events => [],
53
+ :methods => []
54
+ }
55
+ attr_accessor :parent_view
56
+ end
57
+ end
58
+
59
+ def self.bind_id(bind_id)
60
+ bind_setup
61
+ @bind_defs[:id] = bind_id
62
+ end
63
+ def self.bind_var(*arr)
64
+ bind_setup
65
+ @bind_defs[:vars] += arr
66
+ end
67
+ def self.bind_accessor(*arr)
68
+ bind_setup
69
+ @bind_defs[:vars] += arr
70
+ attr_accessor(*arr)
71
+ end
72
+ def self.bind_collection(name, options=nil)
73
+ bind_setup
74
+ @bind_defs[:collections] << {
75
+ varname: name,
76
+ class_name: options.is_a?(Hash) ? options[:class_name] : nil
77
+ }
78
+ attr_accessor(name)
79
+ end
80
+ def self.bind_event(*arr)
81
+ bind_setup
82
+ @bind_defs[:events] += arr
83
+ end
84
+ def self.bind_method(*arr)
85
+ bind_setup
86
+ @bind_defs[:methods] += arr
87
+ end
88
+
89
+ def self.bind_defs
90
+ @bind_defs
91
+ end
92
+
93
+ def initialize
94
+ begin
95
+ @bound_model = `{}`
96
+ self.class.bind_defs()[:vars].each do |var_label|
97
+ if self.send(var_label).is_a?(KnockoutArray)
98
+ ko_arr_observable = self.send(var_label).to_n
99
+ `self.bound_model[var_label] = ko_arr_observable`
100
+ # At some point, figure out how to make it so
101
+ # foomodel.array = ['abc', 123]
102
+ # actually just clears and adds back in stuff via the magic
103
+ # KnockoutArray stuff
104
+ #
105
+ # define_singleton_method("#{var_label}=") do |val|
106
+ # result = super val
107
+ # attribute_did_change(attribute)
108
+ # result
109
+ # end
110
+ else
111
+ `self.bound_model[var_label] = ko.observable(null)`
112
+ `var skipit = '_skip_observable_' + var_label`
113
+ `self.bound_model[var_label].subscribe(function(newValue) {`
114
+ `if (typeof self[skipit] == 'undefined' || self[skipit] != true) {
115
+ self[skipit] = true`
116
+ self.send(var_label + "=", `newValue`)
117
+ `self[skipit] = false`
118
+ `}`
119
+ `});`
120
+ self.add_observer(var_label) do |new_val|
121
+ if new_val.is_a?(Array) or new_val.is_a?(Hash) or new_val.is_a?(ViewModel)
122
+ new_val = new_val.to_n # to_n converts an Opal-based object to a "native" JS object
123
+ end
124
+ `if (typeof self[skipit] == 'undefined' || self[skipit] != true) { `
125
+ `self[skipit] = true`
126
+ `self.bound_model[var_label](new_val)`
127
+ `}`
128
+ `self[skipit] = false`
129
+ end
130
+ end
131
+ end if self.class.bind_defs()[:vars]
132
+
133
+ self.class.bind_defs()[:collections].each do |collection|
134
+ var_label = collection[:varname]
135
+ ko_arr = KnockoutArray.new
136
+ ko_arr.set_collection_class(collection[:class_name], self)
137
+ self.send(var_label + '=', ko_arr)
138
+ ko_arr_observable = self.send(var_label).to_n
139
+ `self.bound_model[var_label] = ko_arr_observable`
140
+ end if self.class.bind_defs()[:collections]
141
+
142
+ self.class.bind_defs()[:events].each do |method_label|
143
+ `self.bound_model[method_label] = function(data, event) { return self['$handle_bind_event'](method_label, event) }`
144
+ end if self.class.bind_defs()[:events]
145
+
146
+ self.class.bind_defs()[:methods].each do |method_label|
147
+ `self.bound_model[method_label] = function(data) { return self['$handle_bind_method'](method_label, data) }`
148
+ end if self.class.bind_defs()[:methods]
149
+
150
+ if self.class.bind_defs()[:id]
151
+ bind_id = self.class.bind_defs()[:id]
152
+ bind_el = `$('[data-bind-id=' + bind_id + ']')`
153
+ `ko.applyBindings(self.bound_model, bind_el.get(0))`
154
+ `bind_el.addClass('ko-bound')`
155
+ end
156
+ rescue Exception => e
157
+ Element.find('body').add_class('ko-debug')
158
+ raise e
159
+ end
160
+ end
161
+
162
+ def self.new_via_collection(hash_obj, parent=nil)
163
+ new_object = self.new
164
+ new_object.parent_view = parent if parent
165
+ hash_obj.each do |k,v|
166
+ if v.is_a?(Array) and new_object.send(k).is_a?(KnockoutArray)
167
+ ko_arr = new_object.send(k)
168
+ ko_arr.clear
169
+ ko_arr.concat(v)
170
+ else
171
+ new_object.send(k + "=", v)
172
+ end
173
+ end
174
+ new_object
175
+ end
176
+
177
+ def self.new_with_parent(parent, *arr)
178
+ new_object = self.new(*arr)
179
+ new_object.parent_view = parent
180
+ new_object
181
+ end
182
+
183
+ def bound_model
184
+ @bound_model
185
+ end
186
+
187
+ def handle_bind_event(method_label, event)
188
+ wrapped_event = Event.new(event)
189
+ self.send(method_label, wrapped_event)
190
+ end
191
+
192
+ def handle_bind_method(method_label, data)
193
+ self.send(method_label, data)
194
+ end
195
+
196
+ ### Override the standard to_n feature by passing along the Knockout model obj instead
197
+ def to_n
198
+ bound_model
199
+ end
200
+
201
+ def to_json
202
+ `ko.toJSON(#{@bound_model})`
203
+ end
204
+
205
+ def serialize_js_data
206
+ `ko.toJS(#{@bound_model})`
207
+ end
208
+
209
+ ### The following Ruby observer code was borrowed from Vienna::Observable
210
+ ### https://github.com/opal/vienna
211
+ def add_observer(attribute, &handler)
212
+ unless observers = @attr_observers
213
+ observers = @attr_observers = {}
214
+ end
215
+
216
+ unless handlers = observers[attribute]
217
+ handlers = observers[attribute] = []
218
+ replace_writer_for(attribute)
219
+ end
220
+
221
+ handlers << handler
222
+ end
223
+
224
+ def remove_observer(attribute, handler)
225
+ return unless @attr_observers
226
+
227
+ if handlers = @attr_observers[attribute]
228
+ handlers.delete handler
229
+ end
230
+ end
231
+
232
+ # Triggers observers for the given attribute. You may call this directly if
233
+ # needed, but it is generally called automatically for you inside a
234
+ # replaced setter method.
235
+ def attribute_did_change(attribute)
236
+ return unless @attr_observers
237
+
238
+ if handlers = @attr_observers[attribute]
239
+ new_val = __send__(attribute) if respond_to?(attribute)
240
+ handlers.each { |h| h.call new_val }
241
+ end
242
+ end
243
+
244
+ # private?
245
+ def replace_writer_for(attribute)
246
+ if respond_to? "#{attribute}="
247
+ define_singleton_method("#{attribute}=") do |val|
248
+ result = super val
249
+ attribute_did_change(attribute)
250
+ result
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ # This class is used internally and you shouldn't need to initialize it
258
+ # yourself or worry too much whether an array is a standard array or a
259
+ # KnockoutArray
260
+ #
261
+ class KnockoutArray < Array
262
+ # NOTE: this method has to be run right after a KnockoutArray is first
263
+ # initialized
264
+ def to_n
265
+ array_value = super
266
+
267
+ @ko_observable = `ko.observableArray(array_value)`
268
+
269
+ @ko_observable
270
+ end
271
+
272
+ def to_json
273
+ `ko.toJSON(#{@ko_observable})`
274
+ end
275
+
276
+ def serialize_js_data
277
+ `ko.toJS(#{@ko_observable})`
278
+ end
279
+
280
+ def set_collection_class(class_name, collection_parent)
281
+ @collection_class_name = class_name if class_name
282
+ @collection_parent = collection_parent
283
+ end
284
+
285
+ def collection_check(obj)
286
+ if @collection_class_name and obj.is_a?(Hash)
287
+ Kernel.const_get(@collection_class_name).new_via_collection(obj, @collection_parent)
288
+ else
289
+ obj
290
+ end
291
+ end
292
+
293
+ def concat(other)
294
+ if Array === other
295
+ other = other.to_a
296
+ else
297
+ other = Opal.coerce_to(other, Array, :to_ary).to_a
298
+ end
299
+
300
+ other.each do |item|
301
+ self.send('<<', item)
302
+ end
303
+
304
+ self
305
+ end
306
+
307
+ def <<(obj)
308
+ obj = collection_check(obj)
309
+
310
+ ret = super(obj)
311
+
312
+ `self.ko_observable.push(obj.$to_n())`
313
+
314
+ ret
315
+ end
316
+
317
+ def delete_at(index)
318
+ ret = super(index)
319
+
320
+ unless ret == nil
321
+ `self.ko_observable.splice(index, 1)`
322
+ end
323
+
324
+ ret
325
+ end
326
+
327
+ def delete(obj)
328
+ obj_index = index(obj)
329
+ ret = nil
330
+
331
+ unless obj_index == nil
332
+ ret = super(obj)
333
+ `self.ko_observable.splice(obj_index, 1)`
334
+ end
335
+
336
+ ret
337
+ end
338
+
339
+ def clear()
340
+ ret = super
341
+
342
+ `self.ko_observable.removeAll()`
343
+
344
+ ret
345
+ end
346
+ end
@@ -0,0 +1,10 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'polished/knockout/view_model'
3
+ else
4
+ require 'opal'
5
+ require 'polished/knockout/version'
6
+
7
+ Opal.append_path File.expand_path('../..', __FILE__).untaint
8
+
9
+ require 'polished/knockout/railtie' if defined?(Rails::Railtie)
10
+ end
@@ -0,0 +1 @@
1
+ require 'polished/knockout'
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'polished/knockout/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "polished-knockout"
8
+ spec.version = Polished::Knockout::VERSION
9
+ spec.authors = ["Jared White"]
10
+ spec.email = ["jared@jaredwhite.com"]
11
+ spec.description = %q{An Opal library for creating view models that use Knockout.js for dynamic HTML updates and event handling}
12
+ spec.summary = spec.description
13
+ spec.homepage = "https://github.com/polished-rb/knockout-rb"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'opal', '~> 0.8'
22
+ spec.add_dependency 'opal-jquery', '~> 0.4'
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency 'opal-rspec', '~> 0.4'
26
+ end
@@ -0,0 +1,34 @@
1
+ [
2
+ {
3
+ "first_name": "Jared",
4
+ "last_name": "White",
5
+ "age": 33,
6
+ "user_types": ["Male", "Human"],
7
+ "favorite_foods": [
8
+ {
9
+ "name": "Burrito",
10
+ "origin": "Mexico"
11
+ },
12
+ {
13
+ "name": "Pizza",
14
+ "origin": "Italy"
15
+ }
16
+ ]
17
+ },
18
+ {
19
+ "first_name": "Jasmine",
20
+ "last_name": "Kitty",
21
+ "age": 6,
22
+ "user_types": ["Female", "Cat"],
23
+ "favorite_foods": [
24
+ {
25
+ "name": "Fish",
26
+ "origin": "Ocean"
27
+ },
28
+ {
29
+ "name": "Kibble",
30
+ "origin": "Pet Store"
31
+ }
32
+ ]
33
+ }
34
+ ]
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Opal Knockout - RSpec Runner</title>
5
+ <script src="https://code.jquery.com/jquery-1.8.3.min.js"></script>
6
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
7
+ <%= javascript_include_tag @server.main %>
8
+ </head>
9
+ <body>
10
+ </body>
11
+ </html>
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ #### ViewModel Classes ####
4
+ class UsersView < Knockout::ViewModel
5
+ bind_id "test-users"
6
+ bind_collection :users, class_name: 'UserView'
7
+ end
8
+
9
+ class UserView < Knockout::ViewModel
10
+ bind_accessor :first_name, :last_name, :age, :user_types
11
+ bind_collection :favorite_foods, class_name: 'FavoriteFoodView'
12
+ end
13
+
14
+ class FavoriteFoodView < Knockout::ViewModel
15
+ bind_accessor :name
16
+ bind_var :origin
17
+
18
+ def origin=(val)
19
+ @origin = val
20
+ end
21
+ def origin
22
+ @origin ? @origin.upcase : ""
23
+ end
24
+ end
25
+
26
+ #### Test Routines ####
27
+ describe 'bindings using server data' do
28
+ it 'should have HTTP get support' do
29
+ HTTP.methods.include?(:get)
30
+ end
31
+
32
+ it 'should save an object graph' do
33
+ uv = UserView.new_via_collection(first_name: 'Dick', last_name: 'Tracy', age: 25, user_types: ["Male", "Human"])
34
+
35
+ # Get the JSON string from the object
36
+ test_json = uv.to_json
37
+
38
+ expect(test_json).to include('{"first_name":"Dick"')
39
+ expect(test_json).to include('"user_types":["Male","Human"]')
40
+
41
+ # JSON postback to server would go here :)
42
+
43
+
44
+ # also make sure real JS data objects work too
45
+ test_js_graph = uv.serialize_js_data
46
+
47
+ expect(`test_js_graph.last_name`).to eq('Tracy')
48
+ end
49
+
50
+ async 'should bind a view with view collection from JSON' do
51
+ collection_html = '<div id="bind-userstest" data-bind-id="test-users" style="display:none"><ul data-bind="foreach: users"><li><span data-bind="text: first_name">_</span> loves <span class="favfoods" data-bind="foreach: favorite_foods"><span data-bind="text: name">_</span> from <span data-bind="text: origin">_</span></span></li></ul></div>'
52
+ Element.find('body') << collection_html
53
+
54
+ users_view = UsersView.new
55
+
56
+ req_url = "/spec/fixtures/users.json"
57
+ HTTP.get(req_url).then do |response|
58
+ run_async do
59
+ users_view.users.concat(response.json)
60
+ expect(Element.find('#bind-userstest > ul > li:eq(0) > span:eq(0)').text).to eq('Jared')
61
+ expect(Element.find('#bind-userstest > ul > li:eq(1) > span:eq(0)').text).to eq('Jasmine')
62
+
63
+ expect(users_view.users[0].age).to eq(33)
64
+
65
+ expect(users_view.users[1].user_types[1]).to eq('Cat')
66
+
67
+ expect(users_view.users[0].favorite_foods[0].origin).to eq('MEXICO')
68
+
69
+ expect(Element.find('#bind-userstest > ul > li:eq(0) span.favfoods > span:eq(2)').text).to eq('Pizza')
70
+
71
+ users_view.users[0].favorite_foods[1].name = "Lasagna"
72
+
73
+ expect(Element.find('#bind-userstest > ul > li:eq(0) span.favfoods > span:eq(2)').text).to eq('Lasagna')
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,2 @@
1
+ require 'opal/jquery'
2
+ require 'polished/knockout'
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ #### ViewModel Classes ####
4
+ class TestBindings < Knockout::ViewModel
5
+ attr_accessor :text_var
6
+
7
+ bind_id "test-bindings"
8
+ bind_var :text_var
9
+ bind_accessor :html_var
10
+
11
+ def initialize
12
+ super
13
+ self.text_var = "Var 123"
14
+ end
15
+ end
16
+
17
+ class TestSubviewBindings < Knockout::ViewModel
18
+ bind_id "test-subview-bindings"
19
+ bind_accessor :somesubview, :somesubview2
20
+
21
+ def initialize
22
+ super
23
+
24
+ self.somesubview = TestSubView.new_with_parent(self, 100)
25
+ self.somesubview2 = TestSubView.new_with_parent(self, 200)
26
+ end
27
+ end
28
+
29
+ class TestSubView < Knockout::ViewModel
30
+ bind_accessor :somevar
31
+
32
+ def initialize(additional_number)
33
+ super
34
+
35
+ self.somevar = 12345 + additional_number
36
+ end
37
+
38
+ # multiply the incoming value by 100 and make sure that's reflected in the HTML output
39
+ def somevar=(val)
40
+ @somevar = val * 100
41
+ end
42
+ end
43
+
44
+ class TestCollection < Knockout::ViewModel
45
+ bind_id "test-collection"
46
+ bind_collection :items, class_name: 'TestCollectionItem'
47
+
48
+ def initialize(data)
49
+ super
50
+ self.items.concat(data)
51
+ end
52
+ end
53
+
54
+ class TestCollectionItem < Knockout::ViewModel
55
+ bind_accessor :title
56
+ end
57
+
58
+ #### Test Routines ####
59
+ describe 'knockout bindings' do
60
+ before(:all) do
61
+ @view_html = '<div id="bindtest1" data-bind-id="test-bindings" style="display:none"><span class="text-var" data-bind="text: text_var">_</span><span class="html-var" data-bind="html: html_var"></span></div>'
62
+
63
+ Element.find('body') << @view_html
64
+ @view_model = TestBindings.new
65
+ end
66
+
67
+ it 'should bind a string var' do
68
+ var_element = Element.find('#bindtest1 > span.text-var')
69
+ expect(var_element.text).to eq('Var 123')
70
+ @view_model.text_var = "Var 456"
71
+ expect(var_element.text).to eq('Var 456')
72
+ end
73
+
74
+ it 'should bind an HTML var' do
75
+ var_element = Element.find('#bindtest1 > span.html-var')
76
+ @view_model.html_var = "<strong><em>This is very strong!</em></strong>"
77
+ expect(var_element.find('strong > em').text).to eq('This is very strong!')
78
+ end
79
+
80
+ it 'should bind a subview and support data manipulation on setters' do
81
+ subview_html = '<div id="bindtest2" data-bind-id="test-subview-bindings" style="display:none"><div data-bind="with: somesubview"><span class="text-var" data-bind="text: somevar">_</span></div><div data-bind="with: somesubview2"><span class="text-var" data-bind="text: somevar">_</span></div></div>'
82
+ Element.find('body') << subview_html
83
+ view_model = TestSubviewBindings.new
84
+ expect(Element.find('#bindtest2 > div:eq(0) > span.text-var').text).to eq('1244500')
85
+ view_model.somesubview.somevar = 67890
86
+ expect(Element.find('#bindtest2 > div:eq(0) > span.text-var').text).to eq('6789000')
87
+
88
+ # at one point more than one subview was buggy. make sure this is working!!!
89
+ expect(Element.find('#bindtest2 > div:eq(1) > span.text-var').text).to eq('1254500')
90
+ end
91
+ end
92
+
93
+ describe 'bindings with view collections' do
94
+ it 'should bind a view with view collection objects' do
95
+ collection_html = '<div id="bindcoltest" data-bind-id="test-collection" style="display:none"><ul data-bind="foreach: items"><li><span data-bind="text: title">_</span></li></ul></div>'
96
+ Element.find('body') << collection_html
97
+
98
+ load_array = [{title: 'Name 1'}, {title: 'Name 2'}]
99
+
100
+ view_model = TestCollection.new(load_array)
101
+
102
+ expect(Element.find('#bindcoltest > ul > li:eq(0) > span').text).to eq('Name 1')
103
+ expect(Element.find('#bindcoltest > ul > li:eq(1) > span').text).to eq('Name 2')
104
+
105
+ view_model.items[0].title = "Name 1.0"
106
+
107
+ expect(Element.find('#bindcoltest > ul > li:eq(0) > span').text).to eq('Name 1.0')
108
+
109
+ view_model.items << {title: "Name 3"}
110
+
111
+ expect(Element.find('#bindcoltest > ul > li:eq(2) > span').text).to eq('Name 3')
112
+
113
+ expect(view_model.items[0].parent_view).to eq(view_model)
114
+
115
+ view_model.items << TestCollectionItem.new.tap{|item| item.parent_view = view_model; item.title = "Name 4" }
116
+
117
+ expect(Element.find('#bindcoltest > ul > li:eq(3) > span').text).to eq('Name 4')
118
+ end
119
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: polished-knockout
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jared White
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: opal-jquery
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: opal-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.4'
83
+ description: An Opal library for creating view models that use Knockout.js for dynamic
84
+ HTML updates and event handling
85
+ email:
86
+ - jared@jaredwhite.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - CODE_OF_CONDUCT.md
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - config.ru
98
+ - lib/polished-knockout.rb
99
+ - lib/polished/knockout.rb
100
+ - lib/polished/knockout/helpers/knockout_helpers.rb
101
+ - lib/polished/knockout/railtie.rb
102
+ - lib/polished/knockout/version.rb
103
+ - lib/polished/knockout/view_model.rb
104
+ - polished-knockout.gemspec
105
+ - spec/fixtures/users.json
106
+ - spec/knockout/index.html.erb
107
+ - spec/server_roundtrip_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/view_model_spec.rb
110
+ homepage: https://github.com/polished-rb/knockout-rb
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.4.5
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: An Opal library for creating view models that use Knockout.js for dynamic
134
+ HTML updates and event handling
135
+ test_files:
136
+ - spec/fixtures/users.json
137
+ - spec/knockout/index.html.erb
138
+ - spec/server_roundtrip_spec.rb
139
+ - spec/spec_helper.rb
140
+ - spec/view_model_spec.rb