2 ko.subscription = function (target, callback, disposeCallback) {
4 this.callback = callback;
5 this.disposeCallback = disposeCallback;
6 this.isDisposed = false;
7 ko.exportProperty(this, 'dispose', this.dispose);
9 ko.subscription.prototype.dispose = function () {
10 this.isDisposed = true;
11 this.disposeCallback();
14 ko.subscribable = function () {
15 ko.utils.setPrototypeOfOrExtend(this, ko_subscribable_fn);
16 ko_subscribable_fn.init(this);
19 var defaultEvent = "change";
21 // Moved out of "limit" to avoid the extra closure
22 function limitNotifySubscribers(value, event) {
23 if (!event || event === defaultEvent) {
24 this._limitChange(value);
25 } else if (event === 'beforeChange') {
26 this._limitBeforeChange(value);
28 this._origNotifySubscribers(value, event);
32 var ko_subscribable_fn = {
33 init: function(instance) {
34 instance._subscriptions = {};
35 instance._versionNumber = 1;
38 subscribe: function (callback, callbackTarget, event) {
41 event = event || defaultEvent;
42 var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
44 var subscription = new ko.subscription(self, boundCallback, function () {
45 ko.utils.arrayRemoveItem(self._subscriptions[event], subscription);
46 if (self.afterSubscriptionRemove)
47 self.afterSubscriptionRemove(event);
50 if (self.beforeSubscriptionAdd)
51 self.beforeSubscriptionAdd(event);
53 if (!self._subscriptions[event])
54 self._subscriptions[event] = [];
55 self._subscriptions[event].push(subscription);
60 "notifySubscribers": function (valueToNotify, event) {
61 event = event || defaultEvent;
62 if (event === defaultEvent) {
65 if (this.hasSubscriptionsForEvent(event)) {
67 ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
68 for (var a = this._subscriptions[event].slice(0), i = 0, subscription; subscription = a[i]; ++i) {
69 // In case a subscription was disposed during the arrayForEach cycle, check
70 // for isDisposed on each subscription before invoking its callback
71 if (!subscription.isDisposed)
72 subscription.callback(valueToNotify);
75 ko.dependencyDetection.end(); // End suppressing dependency detection
80 getVersion: function () {
81 return this._versionNumber;
84 hasChanged: function (versionToCheck) {
85 return this.getVersion() !== versionToCheck;
88 updateVersion: function () {
89 ++this._versionNumber;
92 limit: function(limitFunction) {
93 var self = this, selfIsObservable = ko.isObservable(self),
94 ignoreBeforeChange, previousValue, pendingValue, beforeChange = 'beforeChange';
96 if (!self._origNotifySubscribers) {
97 self._origNotifySubscribers = self["notifySubscribers"];
98 self["notifySubscribers"] = limitNotifySubscribers;
101 var finish = limitFunction(function() {
102 self._notificationIsPending = false;
104 // If an observable provided a reference to itself, access it to get the latest value.
105 // This allows computed observables to delay calculating their value until needed.
106 if (selfIsObservable && pendingValue === self) {
107 pendingValue = self();
109 ignoreBeforeChange = false;
110 if (self.isDifferent(previousValue, pendingValue)) {
111 self._origNotifySubscribers(previousValue = pendingValue);
115 self._limitChange = function(value) {
116 self._notificationIsPending = ignoreBeforeChange = true;
117 pendingValue = value;
120 self._limitBeforeChange = function(value) {
121 if (!ignoreBeforeChange) {
122 previousValue = value;
123 self._origNotifySubscribers(value, beforeChange);
128 hasSubscriptionsForEvent: function(event) {
129 return this._subscriptions[event] && this._subscriptions[event].length;
132 getSubscriptionsCount: function (event) {
134 return this._subscriptions[event] && this._subscriptions[event].length || 0;
137 ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
138 if (eventName !== 'dirty')
139 total += subscriptions.length;
145 isDifferent: function(oldValue, newValue) {
146 return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue);
149 extend: applyExtenders
152 ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe);
153 ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend);
154 ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount);
156 // For browsers that support proto assignment, we overwrite the prototype of each
157 // observable instance. Since observables are functions, we need Function.prototype
158 // to still be in the prototype chain.
159 if (ko.utils.canSetPrototype) {
160 ko.utils.setPrototypeOf(ko_subscribable_fn, Function.prototype);
163 ko.subscribable['fn'] = ko_subscribable_fn;
166 ko.isSubscribable = function (instance) {
167 return instance != null && typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
170 ko.exportSymbol('subscribable', ko.subscribable);
171 ko.exportSymbol('isSubscribable', ko.isSubscribable);