Knockout.js calling method outside of view model - javascript

I want to make my data accessible outside of my view model. So I created a view model object but I'm having trouble binding its properties. Note that everything is working properly inside my view model.
Basically a simplified pseudo-code:
function Users() {
name;
date;
}
function userHealthModel() {
function createUsers() { new Users[] };
}
self.userModel = ko.observable(new userHealthModel());
self.userModel.createUsers();
If I call the createUsers method inside my model my bind works fine.
Here is a jsFiddle, note my problem is all the way at the end of the JS, I commented it:
http://jsfiddle.net/fourgates/jpk22/1/
I'm new to JS and KO. not really sure how to use $root, $parent, etc. Please help a fellow programming enthusiast! Many thanks in advance!

I'm still not 100% sure if I understand what you're trying to do, but here are some thoughts about the code in your fiddle:
If you have something like
var self = this;
in the global scope (= not in a function), this points to the window object. Therefore this does not make any sense.
self.userModel = ko.observable(new userHealthModel());
Creating an observable of a view model is not necessary - you don't expect the whole model to change, right? It will always stay a user model and not suddenly become a "message model" or whatever.
If you want to call a method of your view model from the outside, just make an instance:
var userModel = new userHealthModel();
userModel.createUsers();
// Use "userModel" to access the methods and properties
// like you're using "self" inside the view model:
userModel.users2()[1].userId(5);
// now apply the binding to THE SAME view model
ko.applyBindings(userModel);
http://jsfiddle.net/jpk22/3/
If this isn't what you were looking for, let me know!

Related

What's the secret to data-binding?

For most JS frameworks and libraries, the value they bring are often in the form of a new structure as to how to build an application (Backbone, React), or new ideas that effectively power-up the language (Angular), or simply the methods they offer are well tested, fast, and really convenient (jQuery).
Usually the ideas and methods they provide are pretty straightforward usage of JavaScript, but with a very clever team behind it that find interesting ways to do things which you can think through and get a solid guess as to how the guts work.
However, I've been unable to think through the ability to two-way bind JS models to view components. What is the secret sauce at the heart of this feature that makes this work? Changing an internal variable from a user input is simple, but what about the reverse? How would you be able to "know" when a JS variable has changed in order to update the display instantly? Surely it can't be polling, so what then?
Whenever a block of your JS runs that angular triggered it will run a digest cycle when the block finishes executing. This basically checks all the values that might of changed and would require updates to the view.
If angular didn't trigger the code then it won't know that something might of changed so your bindings can get out of sync. For example if you run something like this
setTimeout(function() {$scope.myValue = '123'});
Angular won't know that myValue changed and it actually won't update the view. That's why Angular has it's own services for doing everything. e.g. $timeout or $http.
If you have some callback function that Angular doesn't know about then you can manually tell it to check for changes by calling $scope.$apply()
there are several ways to do it. Object.observe is great, but lacks good support. You can poll for values as well, keeping a 2nd copy of the object around to compare. You can also write your own explicit set/get methods to update the model like backbone does.
One neat method i use a lot is using getters/setters to keep the model synced to the dom:
//a demo "model" of data:
model = {
name: "Fred"
};
function change(k,v){alert([k,v]);} // a stand-in change monitor for demo
// iterate model and replace values with getter/setter combos:
Object.keys(model).forEach(function(key) {
var val = model[key];
delete model[key];
Object.defineProperty(model, key, {
get: function() {
return val;
},
set: function(v) {
val = v;
change(key, val);
} //call change upon setting
});
change(key, val); //update view "onload"
}); // alerts "Fred";
//update model (fires change() with "name" and "sally" arguments:
model.name="sally"; // alerts "sally";
the change function is quite simple and for your case should just find elements bound to keys.
the advantage here is that you don't need special custom CRUD methods, you can just modify the object properties via assignment like it's 1999. It also doesn't poll, and works correctly all the way back to IE9 and any other ES5 environments. It's the simplest way to bind JS>DOM (afaik) without custom methods.
It does have some limits: nested objects are tricky to get/set upon, you can't do the whole object at once, you can only "watch" primitives. Arrays are a problem too: you can't really replace expando properties with getters/setters without side-effects. But, upon a relatively flat collection of JSON-safe data, get/set works a charm and needs no complex libs to get operational.
checkout a complete example using this method: http://pagedemos.com/xg3szbguqnwu/4
I can speak to how it's done in Backbone, which has a relatively low-level perspective on data-binding.
It's a combination of 1. the library having control over attribute setter methods 2. invoking callback functions when attributes change (e.g. by dispatching events) in order to update the UI.
The essential pseudocode is this:
class Model:
method set(name, value):
if value != this.attributes[name]
this.triggerEvent('change', name, value)
this.attributes[name] = value
m = new Model()
someInputWidget.onEvent('userChangedInput', function(value) {
m.set(someInputWidget.name, value)
})
m.onEvent('change', function(name, value) {
getInputWidgetByName(name).setValue(value)
})
Backbone does not do any data binding to the UI, but you can refer to Backbone's annotated source for the actual event-dispatching implementation.

JavaScript variable scope - proper use

I read this style guide for angular from johnpapa. There is a snippet:
/*
* recommend
* Using function declarations
* and bindable members up top.
*/
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';
activate();
function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
So my question is in functions activate() and getAvengers(), they both reference variable (dataservice) and function (getAvengers()) outside of their scope. Is this proper use? Should I bind these 2 in the variable vm instead, e.g:
vm.getAvengers = getAvengers;
vm.dataservice = dataservice;
...
function activate() {
return vm.getAvengers().then(....);
}
function getAvengers() {
return vm.dataservice.getAvengers().then(.....);
}
Specifically for your case
Would say if you are meaning to use this within angular app would recommend not exposing the service, exposing it through this object does not add value and might down the road, when a less experienced developer modifies your code, might result in wonky access to shared dependencies.
If you want access to the dataservice objects functionality across multiple entities then register it as an angular service, and inject it to the different entities that need it.
In General
Both of the ways you are describing are perfectly correct use, but as is usually the case the answer which to use is "it depends."
Why you would use one for another would be if you wanted to expose the variable externally (i.e. if you wanted to let others access that object through the returned object, expecting others to dynamically change the service on your object)
So in this example you should ask yourself a few question
Do I want to expose this object through another object or do I want to let angular DI pass this along to the other controllers that need this functionality
Do I want to allow external entities to modify this object
Does exposing this service through my object make the use of the perceived use of this object more confusing?
But again for this particular case you should not expose it through your object ( through your variable vm, which is bound to the return object this, in this case )
The vm is a acronym for a view model (a object representation of your view) it is meant to be used within your view to bind elements, ui events to it. The dataservice and the logger seems to nothing to do with the view at all, they are just services used within a controller. If you assign them to the vm then you probably create a tightly coupling between your view and services thus it seems like a not a very good idea to me. You can think about the VM as a interface (glue) between your view and controller.
Here is a picture of the interactions between view model, controller, view and services.

Passing object available in the template into the {{render}} helper doesn't seem to work

I have an object defined globally as App.configObj which contains a property data. Inside a view's template I can use {{App.configObj.data}} to display the value and it works fine.
Inside that same template, I use {{render "viewC" model config=App.configObj}} to render a similar view, but the config property on that view remains null on didInsertElement. Other arguments set to primitive values are correctly set at that point.
Since App.configObj is definitely available in that context, shouldn't I be able to pass it into that view?
Here is the jsbin that illustrates the situation: http://emberjs.jsbin.com/misiyaki/12/edit
If you comment out the render call for ViewC, you can see that {{App.configObj.data}} renders just fine in the template.
My goal is to use an object encapsulating several properties to configure the view, so I need to be able to pass that object in. I spent a lot of time searching for similar content online but didn't find anyone trying this.
What am I missing?
Thanks!
I understand your struggle here with not being able to pass in a property in your render code... but in this case it doesn't seem that that is truly necessary.
Here is a fiddle with some changes to show you another way, that is essentially the same thing if i understood your intentions correctly. http://emberjs.jsbin.com/misiyaki/15/edit
The new code for your view:
App.ViewCView = Em.View.extend({
name: 'testName',
config: function () {
return App.configObj;
}.property(),
data: function () {
return this.get('config.data')
}.property('config'),
templateName: 'view-c'
});
Hope this helps!

Unable to call refresh() or any of View class methods on created instance

In my ExtJS 4.0 app, I have a view already created and initialized, but from one of my controllers, I'm trying to update it using refresh() after creating its instance within controller.
//Following code is within a controller.
var myView = this.getView('MyView').create();
console.log(myView); //this logs entire Map with methods and other members.
myView.refresh(); //doesn't work.
Basically, the logged myView object shows that data within the store is updated, but same is not reflected on the page, hence I tried calling refresh() on View object, but that throws an error saying that method is undefined.
What's wrong here?
P.S.: I'm new to ExtJS.
This may happen if your view is not rendered yet. Try this:
myView.on('afterrender', function (self) {
self.refresh()
});

Using Base2 with KnockoutJS View Models

I am using Base2 as a means to allow us to easily do inheritance in our system, aswell as using KnockoutJS for some UI interactions.
We have defined a base class for our ViewModels
BaseViewModel = Base.extend({
...
});
Which we then extend for our view models:
ExampleViewModel = BaseViewModel.extend({
text: ko.observable("")
});
However there seems to be a problem. When you create 2+ instances of the view model (say if you are pushing them in to an observableArray and using templates to build up a UI) it seems like any changes made to a bound field, updates all view models rather than just the one it's bound to.
Does anybody know why this might be?
Because the extension is not actually instantiating a new observable, its just copying the reference.
I think you can do something like this:
ExampleViewModel = BaseViewModel.extend({
constructor: function() {
this.text = ko.observable("");
}
});
Not as nice though as normal Base2 syntax, but just a limitation in how Knockout is implemented due to issues with properties.

Categories

Resources