knockout-rails 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ spec/dummy/db/*.sqlite3
6
6
  spec/dummy/log/*.log
7
7
  spec/dummy/tmp/
8
8
  spec/dummy/public/assets/
9
+ Gemfile.lock
data/Gemfile CHANGED
@@ -10,3 +10,7 @@ group :development, :test do
10
10
  end
11
11
 
12
12
  gem 'jquery-rails' # For the dummy app
13
+
14
+ # Bindings assets
15
+ gem 'pakunok' # Provides some assets for the bindings
16
+ gem 'ios-checkboxes'
data/HISTORY.md CHANGED
@@ -1,7 +1,13 @@
1
- # 0.0.2 - in progress
1
+ # 0.0.3 - in progress
2
2
 
3
3
  - Support collections (fetch multiple records)
4
4
  - Client side validation
5
+ - JST templating support
6
+
7
+ # 0.0.2 - 26 November 2011
8
+
9
+ - do not require bindings automatically
10
+ - includes bindings: autosave, inplace, onoff, color
5
11
 
6
12
  # 0.0.1 - 17 November 2011
7
13
  Initial release. Bare bones moved over from other project. Includes:
data/README.md CHANGED
@@ -9,12 +9,12 @@ This provides a set of conveniences for you to use more like Backbone or Spine,
9
9
  Add it to your Rails application's `Gemfile`:
10
10
 
11
11
  ```ruby
12
- gem 'knockout'
12
+ gem 'knockout-rails'
13
13
  ```
14
14
 
15
15
  Then `bundle install`.
16
16
 
17
- Reference `knockout` from your JavaScript as you normally do.
17
+ Reference `knockout` from your JavaScript as you normally do with Rails 3.1 Assets Pipeline.
18
18
 
19
19
 
20
20
  # Usage
@@ -71,6 +71,22 @@ Now let's see how we can show the validation errors on the page and bind everyth
71
71
  %span.inline-error{:data=>{:bind=>'visible: errors.name, text: errors.name'}}
72
72
  ```
73
73
 
74
+ ## Bindings
75
+
76
+ This gem also includes useful bindings that you may find useful in your application.
77
+ For example, you can use `autosave` binding by requiring `knockout/bindings/autosave`.
78
+
79
+ Or if you want to include all of the bindings available, then require `knockout/bindings/all`.
80
+
81
+ Please see the specs for more detailed instruction on how to use specific binding.
82
+
83
+ The list of currently available bindings:
84
+
85
+ - `autosave` - automatically persists the model whenever any of its attributes change.
86
+ Apply it to a `form` element. Examples: `autosave: page`, `autosave: {model: page, when: page.isEnabled, unless: viewModel.doNotSave }`.
87
+ - `inplace` - converts the input elements into inplace editing with 'Edit'/'Done' buttons. Apply it on `input` elements similarly to the `value` binding.
88
+ - `color` - converts an element into a color picker. Apply it to a `div` element: `color: page.fontColor`. Depends on [pakunok](https://github.com/dnagir/pakunok) gem (specifically - its `colorpicker` asset).
89
+ - `onoff` - Converts checkboxes into iOS on/off buttons. Example: `onoff: page.isPublic`. It depends on [ios-chechboxes](https://github.com/dnagir/ios-checkboxes) gem.
74
90
 
75
91
  # Development
76
92
 
@@ -1,7 +1,4 @@
1
1
  //=require knockout/sugar-1.1.1
2
2
  //=require knockout/knockout
3
3
  //=require knockout/knockout.mapping
4
- //=require jquery
5
- //=require knockout/observables
6
4
  //=require knockout/model
7
- //=require knockout/bindings
@@ -0,0 +1 @@
1
+ //=require_tree ./
@@ -0,0 +1,26 @@
1
+ valOrDefault = (val, def) ->
2
+ if val?
3
+ ko.utils.unwrapObservable val
4
+ else
5
+ def
6
+
7
+ ko.bindingHandlers.autosave =
8
+ init: (element, valueAccessor, allBindingsAccessor, viewModel) ->
9
+ form = $(element)
10
+ form.submit (e) -> e.preventDefault()
11
+
12
+ options = valueAccessor()
13
+ [ifYes, ifNo, model] = [options.when, options.unless, options.model]
14
+
15
+ model = options unless ifYes? or ifNo?
16
+
17
+ throw "Pls provide either MODEL object or: {model: yourModel, when: optionalTrueCondition, unless: optionalFalseCondition}" unless model.save
18
+
19
+ doSave = model.save.debounce(500) # Not too fast
20
+
21
+ # subscribe to all notifications of all the observable fields of the model
22
+ observables = Object.keys(model).filter((x)->ko.isObservable model[x]).map (x)->model[x]
23
+ observables.forEach (o) -> o.subscribe ->
24
+ ye = valOrDefault ifYes, true
25
+ na = valOrDefault ifNo, false
26
+ doSave() if ye and not na
@@ -0,0 +1,21 @@
1
+ #= require pakunok/colorpicker
2
+
3
+ valOf = (va)-> ko.utils.unwrapObservable(va()) or ''
4
+
5
+ picker =
6
+ init: (element, valueAccessor) ->
7
+ val = valOf valueAccessor
8
+ el = $(element)
9
+ el.addClass('color').css('backgroundColor', val).ColorPicker
10
+ color: val
11
+ onChange: (hsb, hex, rgb) ->
12
+ newVal = "rgb(#{rgb.r}, #{rgb.g}, #{rgb.b})"
13
+ valueAccessor() newVal
14
+
15
+ update: (element, valueAccessor) ->
16
+ newValue = valOf valueAccessor
17
+ $(element).css('backgroundColor', newValue)
18
+
19
+
20
+
21
+ ko.bindingHandlers.color = picker
@@ -0,0 +1,44 @@
1
+ showInput = (element, showOrHide, valueAccessor) ->
2
+ el = $(element)
3
+ editable = el.next()
4
+ button = editable.next()
5
+ editable.toggle(!showOrHide)
6
+ el.toggle(showOrHide)
7
+ val = ko.utils.unwrapObservable valueAccessor()
8
+ if showOrHide
9
+ button.text 'Done'
10
+ el.val val
11
+ else
12
+ button.text 'Edit'
13
+ editable.text val
14
+
15
+
16
+ toggle = (element, valueAccessor) ->
17
+ showingInput = not editing(element)
18
+ showInput element, showingInput, valueAccessor
19
+
20
+ updateValue = (element, valueAccessor) ->
21
+ valueAccessor()( $(element).val() )
22
+
23
+ editing = (element) -> $(element).is(':visible')
24
+
25
+ ko.bindingHandlers.inplace =
26
+ init: (element, valueAccessor) ->
27
+ val = ko.utils.unwrapObservable valueAccessor()
28
+ editable = $("<span class='editable-content' />").insertAfter(element)
29
+ button = $("<a href='#' class='inline-button'>Edit</a>").insertAfter editable
30
+ showInput element, false, valueAccessor
31
+
32
+ button.closest('form').submit ->
33
+ updateValue element, valueAccessor if editing(element)
34
+ showInput element, false, valueAccessor
35
+ button.click (e) ->
36
+ e.preventDefault()
37
+ if editing element
38
+ updateValue element, valueAccessor
39
+ else
40
+ toggle element, valueAccessor
41
+
42
+
43
+ update: (element, valueAccessor) ->
44
+ showInput element, false, valueAccessor
@@ -0,0 +1,20 @@
1
+ #= require ios-checkboxes
2
+
3
+ ko.bindingHandlers.onoff =
4
+ init: (element, valueAccessor, allBindingsAccessor) ->
5
+ initialValue = ko.utils.unwrapObservable valueAccessor()
6
+ $(element).prop('checked', initialValue).iphoneStyle
7
+ handleMargin: 0
8
+ containerRadius: 0
9
+ resizeHandle: false
10
+ resizeContainer: false
11
+ checkedLabel: 'On'
12
+ uncheckedLabel: 'Off'
13
+ onChange: (el, checked) ->
14
+ writer = valueAccessor()
15
+ writer checked
16
+
17
+ update: (element, valueAccessor) ->
18
+ el = $(element)
19
+ val = ko.utils.unwrapObservable valueAccessor()
20
+ el.prop('checked', val)
@@ -1,3 +1,4 @@
1
+ #=require jquery
1
2
 
2
3
  # Module is taken from Spine.js
3
4
  moduleKeywords = ['included', 'extended']
@@ -57,7 +58,7 @@ Ajax =
57
58
  statusCode:
58
59
  422: (xhr, status, errorThrown)->
59
60
  errorData = JSON.parse xhr.responseText
60
- console.debug("Validation error: ", errorData) if console?.debug?
61
+ console?.debug?("Validation error: ", errorData)
61
62
  @updateErrors(errorData)
62
63
 
63
64
  $.ajax(params)
@@ -1,3 +1,3 @@
1
1
  module KnockoutRails
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,78 @@
1
+ #= require knockout/bindings/autosave
2
+
3
+ class Page extends ko.Model
4
+ @configure 'page'
5
+
6
+ describe "AutoSave", ->
7
+ beforeEach ->
8
+ jasmine.Ajax.useMock()
9
+ jasmine.Clock.useMock()
10
+
11
+ prepare = (bindings, viewModel) ->
12
+ page = new Page { name: 'Home' }
13
+ setFixtures "<form id='autosave' data-bind='autosave: #{bindings}' />"
14
+ form = $("#autosave")[0]
15
+ viewModel = if viewModel
16
+ ko.utils.extend viewModel, {page: page}
17
+ else
18
+ {page: page}
19
+ ko.applyBindings viewModel, form
20
+ page
21
+
22
+
23
+
24
+ it "should call 'save' when property changes and use delay", ->
25
+ model = prepare "page"
26
+
27
+ model.name 'Index'
28
+ jasmine.Clock.tick 100
29
+ expect(mostRecentAjaxRequest()).toBeFalsy()
30
+
31
+ jasmine.Clock.tick 5000
32
+ expect(mostRecentAjaxRequest()).toBeTruthy()
33
+
34
+
35
+ it "should not save when 'when' condition is falsy", ->
36
+ model = prepare "{model: page, when: shouldSave}", {shouldSave: false}
37
+
38
+ model.name 'Index'
39
+ jasmine.Clock.tick 5000
40
+ expect(mostRecentAjaxRequest()).toBeFalsy()
41
+
42
+ it "should save when 'when' condition is truthy", ->
43
+ model = prepare "{model: page, when: shouldSave}", shouldSave: ko.observable(true)
44
+ model.name 'Index'
45
+ jasmine.Clock.tick 5000
46
+ expect(mostRecentAjaxRequest()).toBeTruthy()
47
+
48
+ it "should save when 'unless' condition is falsy", ->
49
+ model = prepare "{model: page, unless: dontSave}", dontSave: false
50
+ model.name 'Index'
51
+ jasmine.Clock.tick 5000
52
+ expect(mostRecentAjaxRequest()).toBeTruthy()
53
+
54
+ it "should not save when 'unless' condition is truthy", ->
55
+ model = prepare "{model: page, unless: dontSave}", dontSave: true
56
+ model.name 'Index'
57
+ jasmine.Clock.tick 5000
58
+ expect(mostRecentAjaxRequest()).toBeFalsy()
59
+
60
+ it "should save with when=true, unless=false", ->
61
+ model = prepare "{model: page, when: shouldSave, unless: dontSave}", shouldSave: true, dontSave: false
62
+ model.name 'Index'
63
+ jasmine.Clock.tick 5000
64
+ expect(mostRecentAjaxRequest()).toBeTruthy()
65
+
66
+ it "should not save with when=false, unless=false", ->
67
+ model = prepare "{model: page, when: shouldSave, unless: dontSave}", shouldSave: false, dontSave: false
68
+ model.name 'Index'
69
+ jasmine.Clock.tick 5000
70
+ expect(mostRecentAjaxRequest()).toBeFalsy()
71
+
72
+
73
+ it "should not save with when=true, unless=true", ->
74
+ model = prepare "{model: page, when: shouldSave, unless: dontSave}", shouldSave: true, dontSave: true
75
+ model.name 'Index'
76
+ jasmine.Clock.tick 5000
77
+ expect(mostRecentAjaxRequest()).toBeFalsy()
78
+
@@ -0,0 +1,35 @@
1
+ #= require knockout/bindings/color
2
+
3
+ describe "Color picker", ->
4
+ colorise = (color)->
5
+ setFixtures "<div id='color' data-bind='color: color' />"
6
+ el = $ '#color'
7
+ ko.applyBindings {color: color or ko.observable("rgb(1, 2, 3)")}, el[0]
8
+ el
9
+
10
+ afterEach ->
11
+ # It adds itself to the body, so is outisde of the fixure
12
+ $(".colorpicker").remove()
13
+
14
+ it "should set the initial background color", ->
15
+ el = colorise()
16
+ expect(el.css 'backgroundColor').toBe 'rgb(1, 2, 3)'
17
+
18
+ it "should add 'color' class to element", ->
19
+ el = colorise()
20
+ expect(el).toBe ".color"
21
+
22
+
23
+ it "should reflect the changed color in the UI", ->
24
+ color = ko.observable "rgb(1, 2, 3)"
25
+ el = colorise color
26
+ color "rgb(4, 5, 6)" # Modify the color here
27
+ expect(el.css 'backgroundColor').toBe 'rgb(4, 5, 6)'
28
+
29
+
30
+ it "should update the value when user changes the color", ->
31
+ color = ko.observable "rgb(1, 2, 3)"
32
+ el = colorise color
33
+ $(".colorpicker").data('colorpicker').onChange "hsb", "hex", {r: 4, g: 5, b: 6}
34
+ expect(color()).toBe 'rgb(4, 5, 6)'
35
+
@@ -0,0 +1,42 @@
1
+ #= require knockout/bindings/inplace
2
+
3
+ describe "In-Place edit", ->
4
+
5
+ inplacify = (val) ->
6
+ setFixtures "<input id='el' data-bind='inplace: val' />"
7
+ el = $('#el')
8
+ ko.applyBindings { val: val or ko.observable() }, @el[0]
9
+ el
10
+
11
+ it "should hide input initially", ->
12
+ el = inplacify()
13
+ expect(el).toBeHidden()
14
+
15
+ it "should show the current value", ->
16
+ val = ko.observable 'hi there'
17
+ el = inplacify val
18
+ expect(el.parent().find '.editable-content').toHaveText 'hi there'
19
+
20
+ it "should create an Edit link", ->
21
+ el = inplacify()
22
+ expect(el.parent()).toContain "a.inline-button"
23
+
24
+ it "should show input when clicking the Edit link", ->
25
+ el = inplacify()
26
+ el.parent().find('a.inline-button').click()
27
+ expect(el).toBeVisible()
28
+
29
+ it "should not update the value on 'change' event", ->
30
+ val = ko.observable 'Initial'
31
+ el = inplacify val
32
+ el.val 'from dom'
33
+ el.change()
34
+ expect(val()).toBe 'Initial'
35
+
36
+ it "should update the value clicking Done", ->
37
+ val = ko.observable 'initial'
38
+ el = inplacify val
39
+ el.parent().find('.inline-button').click() # Edit
40
+ el.val('updated')
41
+ el.parent().find('.inline-button').click() # Done
42
+ expect( val() ).toBe 'updated'
@@ -0,0 +1,11 @@
1
+ #= require knockout/bindings/onoff
2
+
3
+ describe "iOS Style Checkboxes", ->
4
+ iphonify = ->
5
+ setFixtures "<input id='check' type='checkbox' data-bind='onoff: isVisible' />"
6
+ el = $ '#check'
7
+ ko.applyBindings {isVisible: ko.observable(true)}, el[0]
8
+ el
9
+
10
+ it "should convert checkboxe into button", ->
11
+ expect(iphonify().parent()).toBe ".iPhoneCheckContainer"
@@ -1,3 +1,6 @@
1
1
  #=require knockout
2
2
  #=require_tree ./support
3
3
  #=require_tree ./
4
+
5
+ beforeEach ->
6
+ clearAjaxRequests()
@@ -1,4 +1,4 @@
1
- // Knockout JavaScript library v1.3.0ctp
1
+ // Knockout JavaScript library v1.3.0rc
2
2
  // (c) Steven Sanderson - http://knockoutjs.com/
3
3
  // License: MIT (http://www.opensource.org/licenses/mit-license.php)
4
4
 
@@ -567,7 +567,12 @@ ko.exportSymbol('ko.utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNo
567
567
 
568
568
  // Go to html and back, then peel off extra wrappers
569
569
  // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
570
- div.innerHTML = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
570
+ var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
571
+ if (typeof window['innerShiv'] == "function") {
572
+ div.appendChild(window['innerShiv'](markup));
573
+ } else {
574
+ div.innerHTML = markup;
575
+ }
571
576
 
572
577
  // Move to the right depth
573
578
  while (wrap[0]--)
@@ -604,6 +609,7 @@ ko.exportSymbol('ko.utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNo
604
609
  })();
605
610
 
606
611
  ko.exportSymbol('ko.utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
612
+ ko.exportSymbol('ko.utils.setHtml', ko.utils.setHtml);
607
613
  ko.memoization = (function () {
608
614
  var memos = {};
609
615
 
@@ -712,7 +718,7 @@ function applyExtenders(requestedExtenders) {
712
718
  ko.exportSymbol('ko.extenders', ko.extenders);
713
719
  ko.subscription = function (callback, disposeCallback) {
714
720
  this.callback = callback;
715
- this.disposeCallback = disposeCallback;
721
+ this.disposeCallback = disposeCallback;
716
722
  ko.exportProperty(this, 'dispose', this.dispose);
717
723
  };
718
724
  ko.subscription.prototype.dispose = function () {
@@ -721,7 +727,7 @@ ko.subscription.prototype.dispose = function () {
721
727
  };
722
728
 
723
729
  ko.subscribable = function () {
724
- this._subscriptions = [];
730
+ this._subscriptions = {};
725
731
 
726
732
  ko.utils.extend(this, ko.subscribable['fn']);
727
733
  ko.exportProperty(this, 'subscribe', this.subscribe);
@@ -730,28 +736,42 @@ ko.subscribable = function () {
730
736
  ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);
731
737
  }
732
738
 
739
+ var defaultEvent = "change";
740
+
733
741
  ko.subscribable['fn'] = {
734
- subscribe: function (callback, callbackTarget) {
742
+ subscribe: function (callback, callbackTarget, event) {
743
+ event = event || defaultEvent;
735
744
  var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
736
745
 
737
746
  var subscription = new ko.subscription(boundCallback, function () {
738
- ko.utils.arrayRemoveItem(this._subscriptions, subscription);
747
+ ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
739
748
  }.bind(this));
740
- this._subscriptions.push(subscription);
749
+
750
+ if (!this._subscriptions[event])
751
+ this._subscriptions[event] = [];
752
+ this._subscriptions[event].push(subscription);
741
753
  return subscription;
742
754
  },
743
755
 
744
- notifySubscribers: function (valueToNotify) {
745
- ko.utils.arrayForEach(this._subscriptions.slice(0), function (subscription) {
746
- // In case a subscription was disposed during the arrayForEach cycle, check
747
- // for isDisposed on each subscription before invoking its callback
748
- if (subscription && (subscription.isDisposed !== true))
749
- subscription.callback(valueToNotify);
750
- });
756
+ notifySubscribers: function (valueToNotify, event) {
757
+ event = event || defaultEvent;
758
+ if (this._subscriptions[event]) {
759
+ ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
760
+ // In case a subscription was disposed during the arrayForEach cycle, check
761
+ // for isDisposed on each subscription before invoking its callback
762
+ if (subscription && (subscription.isDisposed !== true))
763
+ subscription.callback(valueToNotify);
764
+ });
765
+ }
751
766
  },
752
767
 
753
768
  getSubscriptionsCount: function () {
754
- return this._subscriptions.length;
769
+ var total = 0;
770
+ for (var eventName in this._subscriptions) {
771
+ if (this._subscriptions.hasOwnProperty(eventName))
772
+ total += this._subscriptions[eventName].length;
773
+ }
774
+ return total;
755
775
  },
756
776
 
757
777
  extend: applyExtenders
@@ -800,8 +820,9 @@ ko.observable = function (initialValue) {
800
820
 
801
821
  // Ignore writes if the value hasn't changed
802
822
  if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
823
+ observable.valueWillMutate();
803
824
  _latestValue = arguments[0];
804
- observable.notifySubscribers(_latestValue);
825
+ observable.valueHasMutated();
805
826
  }
806
827
  return this; // Permits chained assignments
807
828
  }
@@ -813,9 +834,11 @@ ko.observable = function (initialValue) {
813
834
  }
814
835
  ko.subscribable.call(observable);
815
836
  observable.valueHasMutated = function () { observable.notifySubscribers(_latestValue); }
816
- ko.utils.extend(observable, ko.observable['fn']);
817
-
837
+ observable.valueWillMutate = function () { observable.notifySubscribers(_latestValue, "beforeChange"); }
838
+ ko.utils.extend(observable, ko.observable['fn']);
839
+
818
840
  ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
841
+ ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
819
842
 
820
843
  return observable;
821
844
  }
@@ -878,6 +901,9 @@ ko.observableArray['fn'] = {
878
901
  for (var i = 0; i < underlyingArray.length; i++) {
879
902
  var value = underlyingArray[i];
880
903
  if (predicate(value)) {
904
+ if (removedValues.length === 0) {
905
+ this.valueWillMutate();
906
+ }
881
907
  removedValues.push(value);
882
908
  underlyingArray.splice(i, 1);
883
909
  i--;
@@ -894,6 +920,7 @@ ko.observableArray['fn'] = {
894
920
  if (arrayOfValues === undefined) {
895
921
  var underlyingArray = this();
896
922
  var allValues = underlyingArray.slice(0);
923
+ this.valueWillMutate();
897
924
  underlyingArray.splice(0, underlyingArray.length);
898
925
  this.valueHasMutated();
899
926
  return allValues;
@@ -909,6 +936,7 @@ ko.observableArray['fn'] = {
909
936
  destroy: function (valueOrPredicate) {
910
937
  var underlyingArray = this();
911
938
  var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
939
+ this.valueWillMutate();
912
940
  for (var i = underlyingArray.length - 1; i >= 0; i--) {
913
941
  var value = underlyingArray[i];
914
942
  if (predicate(value))
@@ -938,9 +966,10 @@ ko.observableArray['fn'] = {
938
966
  replace: function(oldItem, newItem) {
939
967
  var index = this.indexOf(oldItem);
940
968
  if (index >= 0) {
969
+ this.valueWillMutate();
941
970
  this()[index] = newItem;
942
971
  this.valueHasMutated();
943
- }
972
+ }
944
973
  }
945
974
  }
946
975
 
@@ -948,6 +977,7 @@ ko.observableArray['fn'] = {
948
977
  ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
949
978
  ko.observableArray['fn'][methodName] = function () {
950
979
  var underlyingArray = this();
980
+ this.valueWillMutate();
951
981
  var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
952
982
  this.valueHasMutated();
953
983
  return methodCallResult;
@@ -1035,7 +1065,9 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
1035
1065
  _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
1036
1066
  });
1037
1067
  var valueForThis = options["owner"] || evaluatorFunctionTarget; // If undefined, it will default to "window" by convention. This might change in the future.
1038
- _latestValue = options["read"].call(valueForThis);
1068
+ var newValue = options["read"].call(valueForThis);
1069
+ dependentObservable.notifySubscribers(_latestValue, "beforeChange");
1070
+ _latestValue = newValue;
1039
1071
  } finally {
1040
1072
  ko.dependencyDetection.end();
1041
1073
  }
@@ -1060,7 +1092,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
1060
1092
  ko.dependencyDetection.registerDependency(dependentObservable);
1061
1093
  return _latestValue;
1062
1094
  }
1063
- }
1095
+ }
1064
1096
  dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; }
1065
1097
  dependentObservable.hasWriteFunction = typeof options["write"] === "function";
1066
1098
  dependentObservable.dispose = function () {
@@ -1114,7 +1146,7 @@ ko.exportSymbol('ko.computed', ko.dependentObservable); // Make "ko.computed" an
1114
1146
  visitedObjects = visitedObjects || new objectLookup();
1115
1147
 
1116
1148
  rootObject = mapInputCallback(rootObject);
1117
- var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined);
1149
+ var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
1118
1150
  if (!canHaveProperties)
1119
1151
  return rootObject;
1120
1152
 
@@ -2042,7 +2074,7 @@ ko.bindingHandlers['options'] = {
2042
2074
  value = [value];
2043
2075
  if (allBindings['optionsCaption']) {
2044
2076
  var option = document.createElement("OPTION");
2045
- option.innerHTML = allBindings['optionsCaption'];
2077
+ ko.utils.setHtml(option, allBindings['optionsCaption']);
2046
2078
  ko.selectExtensions.writeValue(option, undefined);
2047
2079
  element.appendChild(option);
2048
2080
  }
@@ -2554,7 +2586,7 @@ ko.exportSymbol('ko.templateRewriting.applyMemoizedBindingsToNextSibling', ko.te
2554
2586
  if (this.domElement.tagName.toLowerCase() == "script")
2555
2587
  this.domElement.text = valueToWrite;
2556
2588
  else
2557
- this.domElement.innerHTML = valueToWrite;
2589
+ ko.utils.setHtml(this.domElement, valueToWrite);
2558
2590
  }
2559
2591
  };
2560
2592
 
@@ -3068,13 +3100,10 @@ ko.exportSymbol('ko.nativeTemplateEngine', ko.nativeTemplateEngine);(function()
3068
3100
  // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
3069
3101
  // which KO internally refers to as version "2", so older versions are no longer detected.
3070
3102
  var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
3071
- if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']|| jQuery['render']))
3103
+ if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']))
3072
3104
  return 0;
3073
3105
  // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
3074
3106
  try {
3075
- if (jQuery['render'])
3076
- return 3; // Current preview of jsRender
3077
-
3078
3107
  if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
3079
3108
  // Since 1.0.0pre, custom tags should append markup to an array called "__"
3080
3109
  return 2; // Final version of jquery.tmpl
@@ -3090,11 +3119,7 @@ ko.exportSymbol('ko.nativeTemplateEngine', ko.nativeTemplateEngine);(function()
3090
3119
  }
3091
3120
 
3092
3121
  function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
3093
- if (jQueryTmplVersion < 3)
3094
- return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
3095
-
3096
- var renderedHtml = jQuery['render'](compiledTemplate, data, jQueryTemplateOptions);
3097
- return jQuery(ko.utils.parseHtmlFragment(renderedHtml));
3122
+ return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
3098
3123
  }
3099
3124
 
3100
3125
  this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
@@ -3106,8 +3131,7 @@ ko.exportSymbol('ko.nativeTemplateEngine', ko.nativeTemplateEngine);(function()
3106
3131
  if (!precompiled) {
3107
3132
  var templateText = templateSource.text() || "";
3108
3133
  // Wrap in "with($whatever.koBindingContext) { ... }"
3109
- var contextContainer = jQueryTmplVersion == 2 ? "$item" : "$ctx";
3110
- templateText = "{{ko_with " + contextContainer + ".koBindingContext}}" + templateText + "{{/ko_with}}";
3134
+ templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
3111
3135
 
3112
3136
  precompiled = jQuery['template'](null, templateText);
3113
3137
  templateSource['data']('precompiled', precompiled);
@@ -3130,12 +3154,11 @@ ko.exportSymbol('ko.nativeTemplateEngine', ko.nativeTemplateEngine);(function()
3130
3154
  document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
3131
3155
  };
3132
3156
 
3133
- if (jQueryTmplVersion >= 2) {
3134
- var tagContainer = jQueryTmplVersion == 2 ? "tmpl" : "tmplSettings";
3135
- jQuery[tagContainer]['tag']['ko_code'] = {
3157
+ if (jQueryTmplVersion > 0) {
3158
+ jQuery['tmpl']['tag']['ko_code'] = {
3136
3159
  open: "__.push($1 || '');"
3137
3160
  };
3138
- jQuery[tagContainer]['tag']['ko_with'] = {
3161
+ jQuery['tmpl']['tag']['ko_with'] = {
3139
3162
  open: "with($1) {",
3140
3163
  close: "} "
3141
3164
  };
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knockout-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-17 00:00:00.000000000 Z
12
+ date: 2011-11-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sprockets
16
- requirement: &70265769988800 !ruby/object:Gem::Requirement
16
+ requirement: &70109798186500 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70265769988800
24
+ version_requirements: *70109798186500
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: execjs
27
- requirement: &70265769987360 !ruby/object:Gem::Requirement
27
+ requirement: &70109798186040 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70265769987360
35
+ version_requirements: *70109798186040
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jquery-rails
38
- requirement: &70265769986280 !ruby/object:Gem::Requirement
38
+ requirement: &70109798185520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70265769986280
46
+ version_requirements: *70109798185520
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70265769984520 !ruby/object:Gem::Requirement
49
+ requirement: &70109798185040 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70265769984520
57
+ version_requirements: *70109798185040
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rails
60
- requirement: &70265769832120 !ruby/object:Gem::Requirement
60
+ requirement: &70109798184540 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: 3.1.1
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70265769832120
68
+ version_requirements: *70109798184540
69
69
  description: Include the knockout.js and some of its extensions so you can pick what
70
70
  you need. Adds the support for models and interation with the Rails backend.
71
71
  email:
@@ -76,14 +76,17 @@ extra_rdoc_files: []
76
76
  files:
77
77
  - .gitignore
78
78
  - Gemfile
79
- - Gemfile.lock
80
79
  - HISTORY.md
81
80
  - README.md
82
81
  - Rakefile
83
82
  - knockout-rails.gemspec
84
- - lib/assets/javascripts/knockout/bindings.js.coffee
83
+ - lib/assets/javascripts/knockout.js
84
+ - lib/assets/javascripts/knockout/bindings/all.js
85
+ - lib/assets/javascripts/knockout/bindings/autosave.js.coffee
86
+ - lib/assets/javascripts/knockout/bindings/color.js.coffee
87
+ - lib/assets/javascripts/knockout/bindings/inplace.js.coffee
88
+ - lib/assets/javascripts/knockout/bindings/onoff.js.coffee
85
89
  - lib/assets/javascripts/knockout/model.js.coffee
86
- - lib/assets/javascripts/knockout/observables.js.coffee
87
90
  - lib/knockout-rails.rb
88
91
  - lib/knockout-rails/engine.rb
89
92
  - lib/knockout-rails/version.rb
@@ -118,9 +121,11 @@ files:
118
121
  - spec/dummy/public/500.html
119
122
  - spec/dummy/public/favicon.ico
120
123
  - spec/dummy/script/rails
121
- - spec/javascripts/knockout/bindings_spec.js.coffee
124
+ - spec/javascripts/knockout/bindings/autosave_spec.js.coffee
125
+ - spec/javascripts/knockout/bindings/color_spec.js.coffee
126
+ - spec/javascripts/knockout/bindings/inplace_spec.js.coffee
127
+ - spec/javascripts/knockout/bindings/onoff_spec.js.coffee
122
128
  - spec/javascripts/knockout/model_spec.js.coffee
123
- - spec/javascripts/knockout/observables_spec.js.coffee
124
129
  - spec/javascripts/spec.css
125
130
  - spec/javascripts/spec.js.coffee
126
131
  - spec/javascripts/support/mock-ajax.js
@@ -128,7 +133,6 @@ files:
128
133
  - spec/spec_helper.rb
129
134
  - spec/support/global.rb
130
135
  - spec/support/matchers.rb
131
- - vendor/assets/javascripts/knockout.js
132
136
  - vendor/assets/javascripts/knockout/knockout.js
133
137
  - vendor/assets/javascripts/knockout/knockout.mapping.js
134
138
  - vendor/assets/javascripts/knockout/sugar-1.1.1.js
@@ -146,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
146
150
  version: '0'
147
151
  segments:
148
152
  - 0
149
- hash: -209909381352659147
153
+ hash: -2773506954842837936
150
154
  required_rubygems_version: !ruby/object:Gem::Requirement
151
155
  none: false
152
156
  requirements:
@@ -155,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
159
  version: '0'
156
160
  segments:
157
161
  - 0
158
- hash: -209909381352659147
162
+ hash: -2773506954842837936
159
163
  requirements: []
160
164
  rubyforge_project: knockout-rails
161
165
  rubygems_version: 1.8.10
@@ -194,9 +198,11 @@ test_files:
194
198
  - spec/dummy/public/500.html
195
199
  - spec/dummy/public/favicon.ico
196
200
  - spec/dummy/script/rails
197
- - spec/javascripts/knockout/bindings_spec.js.coffee
201
+ - spec/javascripts/knockout/bindings/autosave_spec.js.coffee
202
+ - spec/javascripts/knockout/bindings/color_spec.js.coffee
203
+ - spec/javascripts/knockout/bindings/inplace_spec.js.coffee
204
+ - spec/javascripts/knockout/bindings/onoff_spec.js.coffee
198
205
  - spec/javascripts/knockout/model_spec.js.coffee
199
- - spec/javascripts/knockout/observables_spec.js.coffee
200
206
  - spec/javascripts/spec.css
201
207
  - spec/javascripts/spec.js.coffee
202
208
  - spec/javascripts/support/mock-ajax.js
data/Gemfile.lock DELETED
@@ -1,134 +0,0 @@
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
@@ -1 +0,0 @@
1
- #TODO: Add additional bindings
@@ -1 +0,0 @@
1
- # TODO: add additional observers
@@ -1,2 +0,0 @@
1
- describe "bindings", ->
2
- it "coming soon"
@@ -1,3 +0,0 @@
1
- describe "observables", ->
2
-
3
- it "coming soon"