Giter VIP home page Giter VIP logo

knockoutjs-reactor's Introduction

KO-Reactor

A KnockoutJS plugin that lets you track all changes within a view model seamlessly with the ability to pin point and process them on the fly. It does not require any modifications to the markup or view model itself which makes it ideal for testing and quick prototyping.

Usage:

ko.watch(targetObjectOrFunction, options, evaluatorCallback, context);

or

var property = ko.observable().watch(targetObjectOrFunction, options, evaluatorCallback, context);

targetObjectOrFunction accepts any subscribable or function/object containing the targeted subscribables.
options is only optional and can be replaced by evaluatorCallback the response and evaluation function.

Deep Watcher:
The depth option is used to limit the recursion depth. Its default value 1 limits it to a flat view model but a value of -1, as shown below, will fully unleash it:

var viewModel = { ... };
ko.watch(viewModel, { depth: -1 }, function(parents, child, item) {
	...
});

The callback function provides three arguments:

  1. parents: A list of all parents above the changed property.
  2. child: The child property that changed.
  3. item: Used to determine whether an array item has been added, moved or removed.

Note that for observable arrays, subscriptions are created or destroyed on the fly as new items are added or removed. To keep up with changes in the model structure itself however the mutable option needs to be set to true.

Auto Wrapper:
Setting the wrap option to true will make sure that all fields within the targeted object are wrapped within an observable even if new array items are added later on.

Chaining Support:
The chaining support provided adds in more ways to simplify the code and make it more readable. For example let's consider this simple view model:

var params = {
    p1 = ko.observable(),
    p2 = ko.observable(),
    p3 = ko.observable()
}

To update a variable, say items, whenever any parameter within params changes we would most probably write something along those lines:

var items = ko.observableArray();      	 	        var items = ko.computed(function() {
ko.computed = function() {             	    	        var p1 = self.params.p1();
    var p1 = self.params.p1();         	    	        var p2 = self.params.p2();
    var p2 = self.params.p2();         	OR	            var p3 = self.params.p3();
    var p3 = self.params.p3();         	         
														var newItems = ...;
    var newItems = ...;                     	        return newItems;
    self.items(newItems);               	        }
}                                       

However, using the plugin, we can just watch params right away like so and let it do the rest:

var items = ko.observableArray();         	    	var items = ko.observableArray();
ko.watch(params, function() {              		    items.watch(params, function() {    
    var newItems = ...;                 OR   	    	var newItems = ...;                 
    self.items(newItems);                   	        return newItems;                    
});                                       	      	}); 

Finally adding a bit of fluency to the mix we end up with the single block below:

var items = ko.observableArray().watch(params, function() {
    var newItems = ...;
    return newItems;    
});

Note that by omitting the target parameter we obtain a chainable version of .subscribe. So instead of creating a subscription separately like so:

var someProperty = ko.observable();
someProperty.subscribe(someFunction, options);

We can do away with the redundancy like so:

var someProperty = ko.observable().watch(someFunction, options);

Selective Subscription:
There are many ways we can go about making a selective watcher. Suppose we want to avoid watching the variable p3. To achieve this we could move p3 to another level like so:

var params = {
    p1: ko.observable(),
    p2: ko.observable(),
    ignored: {
        p3: ko.observable() 
    }
};

But this assumes that the depth option is set to 1. To avoid this we can pass in a new object made up of p1 and p2 only:

ko.watch({ 1: params.p1, 2: params.p2 }, function (parents, child, item) {
    ...
}

Or we can just use the hide option this way:

ko.watch(this.params, { hide: params.p3 }, function (parents, child, item) {
    ...
}

The easiest way however is to simply tag p3 as non-watchable like so:

var params = {
    p1: ko.observable(),
    p2: ko.observable(),
    p3: ko.observable().watch(false) 
};

For more advanced cases however we can make use of the beforeWatch option which gives us full control over the subscription process:

ko.watch(this.params, {
    beforeWatch: function (parents, child) {
        if (<some reason not to subscribe>)
            return false; // cancel subscription
    }
}, function (parents, child, item) {
	...
});

Pausable Listener:
Pausing and resuming a reactor on any property can be done like so:

this.data.watch(false);
//...do work
this.data.watch(true);

Dispose/Unwatch:
The watch function returns an object with a "dispose" method you can call to dispose the watch action:

var viewModel = { ... };
var res = ko.watch(viewModel, { depth: -1 }, function(parents, child, item) {
    ...
});

res.dispose();

Once disposed your model will be "unwatched"

Synchronous tracking: (since 1.4.0)
By default when new objects are added to the tree, they are automatically "watched" asynchronously (e.g: in a setTimeout) in order to keep the system more responsive.

Sometimes this behaviour is not "expected", so you can use the async: false option to force watch to happen "inline".

Single notification for multiple array changes vs change by change notifications (since 1.4.0)
By default when items are moved in an array you receive 2 different notifications, the first will report the "deleted item" and the second one will report the "added item".

If you prefer to receive an array of items in a single notification you can use the splitArrayChanges: false option. In that case you will receive an array of item instead of a single item even when there is only one item changed.

Projects using KO-Reactor:
As of now I'm only aware of this excellent undo manager by Stefano Bagnara:

https://github.com/bago/knockout-undomanager

Here's a Plunker showing it in action on an array within an array:

http://plnkr.co/edit/nB2129?p=preview

Reactor is also used (via Undomanager) in Mosaico the first opensource Email Template Editor:

https://github.com/voidlabs/mosaico

Do feel free to let me know if you have anything related to share and I'll gladly mention it here.

Further Notes:
Unlike ko.computed no listeners are created for subcribables within the evaluator function. So we no longer have to be concerned about creating unintended triggers as the code gets more complex.

Yet another usage not mentioned above example involves calling watch on a function as shown below:

ko.watch(function() { ... }, options);

However it is nothing more than a cosmetic enhancement since it only returns a computed of the function being passed in.

For an extensive list of available options please consult the param sections of the source code.

Development

In order to build and minify you can simply call grunt:

grunt

If you want a live rebuild for each changed

grunt develop

If you want to run the test suite:

grunt jasmine

If you want to generate the test runner in order to run the test suite in your browser

grunt jasmine:main:build

knockoutjs-reactor's People

Contributors

bago avatar dependabot[bot] avatar jakiestfu avatar jsearles avatar ziadj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

knockoutjs-reactor's Issues

Reactor recursion doesn't work

The following test case for recursion doesn't appear to work (ko 2.2.1, ko.mapping 2.4.0):

var data = "{\"id\":1,\"items\":[{\"id\":1},{\"id\":2}]}";

var model = ko.mapping.fromJSON(data);

ko.watch(model, { recurse: true }, function () {
    alert('Reactor');
});

// Change the data
model.id(2); // Reacts OK
model.items()[0].id(2); // Does not react

How to "unwatch" / stop watching?

Is there a way to stop watching a tree / unwatch the tree?

I see the code has "unwatch" / "dispose" methods but they seems "internal" and not public: am I missing anything?

Uncaught Subscriptions field (.K) not defined for observable child Error only when using knockout.debug

I have knockout 3.4.1 debug and reactor 1.3.7 and I get this error only when using debug version of knockout.

Uncaught Subscriptions field (.K) not defined for observable child
disposeWatcher @ knockout.reactor.js:233
watchChildren @ knockout.reactor.js:144
(anonymous) @ knockout.reactor.js:179
objectForEach @ knockout-3.4.0.js:62
watchChildren @ knockout.reactor.js:161
(anonymous) @ knockout.reactor.js:249

syntax

Interesting plugin. But I don't understand the ko.observable().watch syntax. By adding watch to ko.subscribable.fn, you make it a method of every observable. So if you have item = ko.observable();, then you could do item.watch(...);, but that wouldn't mean what it seems to. If you want to make it part of ko.observable, it should just be ko.observable.watch.

How can we reset it?

Sorry I'm new to knockout.
I have a view in read mode and an Edit button. When the user clicks it then I display an Edit view, start to watch my viewmodel for change and display a Cancel and a Save button.
If the user click the Cancel button I would like to reset the watching.
How could it be possible?
Thank you!

KO Reactor / KO Undo Manager compatibility (context is not a function)

I am not sure where this issue belongs/must be fixed, so I'm opening in both repositories, since @bago is KO Reactor contributor and KO Undo Manager author.

According to KO Reactor (link), it supports a context option, which is undocumented. Looking at its usages in assignWatcher function, context should be a function.

However, KO Undo Manager is building an empty object (link) as context and is passing it to KO Reactor (link).

This leads to a TypeError: context is not a function error.

At this point, there are 2 options:

  • KO Undo Manager passing an empty function instead of empty object, which is what KO Reactor expects
  • KO Reactor checking if context is a function before executing it, e.g. typeof context === 'function' && context(returnValue) instead of context(returnValue).

Additionally, it might help if KO Reactor's context option is documented to indicate how it should look like.

I am happy to contribute with a PR if needed, I'm just not sure what's the correct way to handle this. Both options would be backwards-compatible.

Automatically watch new objects recursively

I have a deep JSON object that a wrap to nested observables through knockout.wrap (I don't think this is a variable for this issue, but better safe than sorry).

When I do
viewModel.myObject(newObject);
then the observable properties of newObject are not automatically watched by reactor.

Is this something expected? I see other "mutations" for the array are tracked but this one is not.

Cannot ignore observable arrays

It's currently not possible to ignore an observable array within a hierarchy. (The code-path for arrays never does this check.)

Also instead of having a "beta" file would you consider making a separate alpha branch?

New release?

Hi ZiadJ, what about making a new release? It's a long time since the last proper release (1.3.0) and a lot of fixes/changes.

If you need help I can do that if you give me privileges to the project.

Issue with KO 3.3.0 and the minified reactor

The min.js is missing a "break" after the 3.3.0 switch "match" so it uses the ".K" variable for both 3.3.0 and 3.4.0... The "non-minified" version works fine.

So it seems something gone wrong with the minification process.

_fieldName not set?

Maybe it's my misunderstanding (or lack of documentation), but I cannot get changed field name in the _fieldName.

Consider this simple fiddle: http://jsfiddle.net/y0ygaszL/1/

Whenever I change a scalar property ("set prop" button), _fieldName is not set neither on child nor on the parent.

When I change width or height property of array element, then array field name is correctly reported in the parent, but the property name itself is still missing on the child.

Also, when array element is removed ("remove" button), the watch callback is not invoked at all.

Would you please comment?

support for _fieldPath/childPath and "observable Objects"

Hi, I'm the ko-undomanager author and I have a feature request.. I'm willing to implement it but I'd like to have some hint from you (and to know if you think my needs are "general purpose" or not).

In the undomanager I currently keep a stack of functions that have references to the "child" and the previous value, so that they simply set the value when you "undo". Now I have to improve it so that I don't keep references to live objects because it keeps "removed" objects live and this break other things. So I'd like to change it so that I remember only the "tree-path" (as a string) and the value (again as a primitive variable/object): e.g: { path: "root.object.array[3].property", oldvalue: "5" }. This would also allow me to put the undo/redo stack on the wire (this is serializable) and do smart things.

Now my issues are:

  1. _fieldName on the parents doesn't work when you use observableObjects. I have models like this one:
    model: {
    objProp: ko.observable(
    prop1: ko.observable(prop1Val),
    prop2: ko.observable(prop2Val)
    ),
    obj2Prop: ko.observable(
    prop1: ko.observable(prop1Val),
    prop2: ko.observable(prop2Val)
    ),
    }
    Currently the "_fieldName only applies to simple Objects or Array and in my case it finds "Functions" (observable) where it only supports Objects or Array.

  2. _fieldName only applies to parents and doesn't tell me the "index" in case my child is in array... so if I loop in the parents I can build "root.object.array.property" but not "root.object.array[3].property" that I need to be able to find again the right object when I execute undo. What about implementing an option so that the watching function also receive the childPath (relative to the root of the watch( and inlcuding indexes for the arrays?

Basic functional test suite

I have hard times figuring out issues in the library and understanding if some code was a feature or a bug.

This library offer a big set of options and I cannot check all of them, but at least I can add some tests for my own use cases. Time and more use case will help improving it.

Bower Accessible

It would be nice to have this as a bower module so that we can pull this down directly into our projects. Any plans for this in the future? If not, I could make a pull request to do it.

Multiple watch break on dispose

var options = { depth: -1 };
var a = ko.watch(model, options, listener);
var b = ko.watch(model, options, listener2);
a.dispose();

listener2 is not called anymore.

I saw that "context" equals "ko" in the internal checks, for both calls.
I thought I could call the 2 watch with different context but I see the code somewhere uses context(returnValue); so context have probably some meaning that I am missing.

Can you document the "context" stuff or explain me how can I have 2 indipendent watchers?
I use knockout-reactor for a custom function in my project, but the same project also uses my undomanager: when I dispose the first watch the undomanager stops working.

hide option still watching

Hi,
In the readme it's mentioned that, to unwatch any observable we can simply pass {hide: path.of.observable} as an option. But when I do this it still watches for the item I specified in the hide option.
Not sure if I'm doing this right but here's a rough fiddle which I put together to check the hide option.

Thanks for the plugin. Really impressed by how much it does with such little code. 👍

"un-watch" functionality...

Hello,
I do not see in your documentation the ability to "un-watch" a view model. Does this automatically occur when ko.cleanNode(...) is called?

Thanks,
Jim

Bad parents in the notifications when sub-objects are replaced

var subModel = {
  c: ko.observable(1),
  d: ko.observable(0)
};

var subModelB = {
  e: ko.observable(0),
  f: ko.observable(1)
};

var model = {
  a: ko.observable(1),
  b: {
    g: ko.observable(0),
    h: ko.observable(0),
  },
  sub: ko.observable(subModel),
};

var w = ko.watch(model, { depth: -1, oldValues: 1, mutable: true, tagFields: true }, listener);

model.sub().c(4); // this is notified correctly

model.sub(subModelB);
model.sub().e(4); // this is notified with the wrong number of parents

Once you replace an observable object the new notifications have one more "parent" that is not expected.

AMD module support

I am trying to load this library using requireJS but It keeps failing with "Uncaught ReferenceError: ko is not defined ".

Any ideas what might cause this?

Tested for Possible Memory Leaks

Have you tested for possible memory leaks since you subscribe to each child node in a list and below? If nodes are removed are their subscriptions cleaned up in such a way that they will not hold on to memory?

KO version check for minimized propery names

disposeWatcher uses "child.H || child._subscriptions;"

For every knockout version the minimized name changes breaking that code.

in 3.0.0 the minimized name is "F"
in 3.1.0 it is "H"
in 3.2.0 it is "M"
in 3.3.0 it is "G"

So it seems the current code is only "fully" compatible with 3.1.0.

adding .watch(false) -- Uncaught ReferenceError: context is not defined -- knockout.reactor.js:31

Not sure if this is a bug in the code or and issue with the documentation? I don't seem to be able to get the .watch(false) to work.

I have a list item that when clicked gets a 'selected=true' set, this is my code that bugs out:

self.selected = ko.observable(false).watch(false);
self.onClick = function(item) { root.unselect_all(); self.selected(true) };

Am I doing it wrong? Or is this a bug?

Won't wrap a property when it has a null value

At around line 176 in watchChildren, there is an if (sub) statement that prevents any wrapping of a property if the property of the object is null. I'm not sure if this is desired behavior in some cases but in my case it was not. The property was null but when bound to a form, changes that the user made weren't invoking my event handler. I removed the if() statement and it worked as I'd expect. And no errors.

So I think we should remove the if (sub) statement so that any changes made to the property value in the UI generate a change event.

Keep notifying changes for replaced objects

var subModel = {
  c: ko.observable(1),
  d: ko.observable(0)
};

var subModelB = {
  e: ko.observable(0),
  f: ko.observable(1)
};

var model = {
  a: ko.observable(1),
  b: {
    g: ko.observable(0),
    h: ko.observable(0),
  },
  sub: ko.observable(subModel),
};

var w = ko.watch(model, { depth: -1, oldValues: 1, mutable: true, tagFields: true }, listener);

model.sub(subModelB);
subModel.c(4); // this is a change in a model being removed by the original tree, but still the listener is notified with this change.

Some "unwatch" is not working correctly.

Deleted items are not unsubscribed

I faced with issue that change event is fired for deleted item. And it caused some unpredictable behavior in my application. Looks like I found a reason for it. I found a place in your code:
(line 145) var subsc = child._subscriptions;
And then you try to delete some subscriptions. As I understand, _subscriptions is knockout stuff, but I have minified version of it, so _subscriptions is underfined and, as result, subscription were not deleted. Also you try to delete subscriptions within ko.utils.arrayForEach and it caused one more issue, so I changed it with for loop.

My issue. I have an object:

{
...
Exits: [
    ...,
    {
        ...
        Next: {
            TargetId: 'value',
            Anchor: 'value2'
        }
        ...
    },
    ...
]
...
}

I deleted an object from Exits array and then added it. When I try to change value of TargetId or Anchor I have two change events instead of one.

Monitoring arrays?

I've found this github project following the post in http://stackoverflow.com/questions/10622707/detecting-change-to-knockout-view-model , and I'm interested in the case where I need to monitor an array both for changes in the observableArray itself (adding/removing items) and the changes in the items of the array. The array items are simple objects, and some of their properties are also observables.

I don't need to monitor the whole model, just this one array.

I've tried using the "latest" source file in the github, and my code looks like this:

ko.watch(mymodel.list, { recurse: true }, function() {
  alert("changed");
});

... but nothing happens.

Is this possible and what would the code look like?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.