dynamic observable classes for knockout - javascript

I am working on a reporting related project, where I need to build lot of reports rendered using KO. All data pulled using AJAX and the model is updated. Currently I am writing tons of js functions to map the models. Something like:
function modelx(child) {
var self = this;
self.Name = ko.observable(child.Name);
self.Relation = ko.observable(child.Relation);
// hundred other properties
};
function modely(child) {
var self = this;
self.Age = ko.observable(child.Age);
self.Relation = ko.observable(child.Relation);
// hundred other properties
};
and after AJAX call, I am filling the observable arrays
for (var i = 0; i < jsn.length; i++)
{
VM.modelxlist().push(new modelx(jsn[i]));
}
for (var i = 0; i < jsn1.length; i++)
{
VM.modelylist().push(new modely(jsn1[i]));
}
Is there any way to avoid the definition of modelx, modely,... such that the model is automatically built without loosing the benefits of this approach while using in HTML? Of course there could be a corner case where I may not get a specific property from server, which I should check on the server side.
Also, at times I may need to add additional computed observables (just to be more flexible)

Why don't you use knockout mapping plugin:
http://knockoutjs.com/documentation/plugins-mapping.html
You would then have something like:
var modelxInstance= ko.mapping.fromJS(child);
There are a few mapping plugins for knockout, the one i like the most is actually this one:
https://github.com/LucasLorentz/knockout.mapper
And the reason is that it is more configurable and it is faster.

I think this is what you want..
With ko.mapping.fromJS method u can automatically observe all the properties from your object..
Take some time to read about that..

Related

jquery ui - call methods with same name on different widgets

Suppose I have created several widgets (mywidget1, mywidget2, ...) and that all have a method with the same name (doSomething).
To invoke the method I can use:
$("#elem").widget1("doSomething");
but this way I need to know the name of the widget (in the example widget1).
If I have an array with multiple instances of the various widgets, how can I invoke on each one the method "doSomething" without knowing the name of the widget?
You can't. Two options for you:
Store them in separate arrays (one array for widget1, another for widget2), or
Store objects in the arrays containing the information about which widget relates to that entry
Here's an example of #2
var list = [
{widget: "widget1", element: $("#elem")},
{widget: "widget2", element: $(".some-selector")},
{widget: "widget2", element: $("#another")},
{widget: "widget1", element: $("div .target")}
];
$.each(list, function(i, entry) {
entry.element[entry.widget]("doSomething");
});
In theory I suppose you could do something like the following:
var widgets = [widget1, widget2, widget3]; // etc, will assume they are defined already
// Access each Widget, now widget points to each of the widgets
// So we dont need to know the actual name like widget1, widget2 etc
widgets.forEach(function (widget) {
$('#elem').widget('doSomething'); // Call the doSomething method of this widget
$('#elem').widget.call(null, 'doSomething'); // Try this if the above fails
$('#elem').call(widget, 'doSomething'); // Or maybe this :)
}
Anyway try the above, of the top of my head im not sure which will work. I think what you are trying to do might be a bit difficult to implement, so sorry if it doesnt work. Hopefully it will :)
you can have an object of functions and get the key value dynamically to use the particular function
widgets = {
widget1 : function(value){return value;},
widget2 : function(value){return value+1},
widget3 : function(value){return value+2}
}; //you can have your list of functions say widgets1,2,3....
for(var k in widgets ){
console.log(widgets[k](1));
} //to get the function name you can use the key name in the object
Example on https://gist.github.com/vishnu667/44063d64f2d1210c26c9
you can also call the function if you know the key
widgets['widget2'](10); //to directly call the function if you know the key
to Dynamically add more widgets use
function add_widget(name,widgetFunction){ //function to add widget
widgets[name]=widgetFunction;
}
add_widget("widget4",function(value){return value+10;}); //adds a new widget to the widgets list
I found this solution:
before:
var tmp = $("#elem").widget1();
myarray[i] = tmp;
now:
var tmp = $("#elem").widget1();
myarray[i] = tmp.data(ui-widget1);
in this way it is possible to directly call the method:
myarray[x].doSomething();
what do you think?
can be an efficient solution?
thanks to all

Ko.computed function updates twice

Due to data being loaded via AJAX I need my data to be updated when data arrives.
On page load I collect projects in database. Then load data for Tasks and Tags depending on which project is selected (self.SelectedProject).
self.Projects = ko.observableArray();
self.Tasks = ko.observableArray();
self.Tags = ko.observableArray();
self.SelectedProject = ko.observable(); // Chosen Project-object...
For initialization I load data for the first Project:
self.SelectedProject(self.Projects()[0]); // Choose first returned Project...
Then I go on to populate my tag-helping arrays:
ko.computed(function () {
// must be ko.computed as else will not update when data arrives for Tags and Tasks (which are likely to be empty at load time)...
// Empty projectAvailableTags before refill...
self.SelectedProject().projectAvailableTags([]);
// First populate current project's "projectAvailableTags"-array with values...
for (var j = 0, jlen = self.Tags().length; j < jlen; j++) {
self.SelectedProject().projectAvailableTags().push(self.Tags()[j].TagName());
}
for (var i = 0, ilen = self.Tasks().length; i < ilen; i++) {
//---- Populate each TaskTag-array with Tags...
for (var j = 0, jlen = self.Tags().length; j < jlen; j++) {
if (self.Tags()[j].TagTaskId() === self.Tasks()[i].TaskId) {
self.Task()[i].TaskTags.push(self.Tags()[j]);
// Populate the different tag-Arrays...
var tagtype = self.Tags()[j].TagType;
switch (tagtype()) {
case 0: self.Tasks()[i].Location().push(self.Tags()[j].TagName()); break;
case 1: self.Tasks()[i].Manager().push(self.Tags()[j].TagName()); break;
case 2: self.Tasks()[i].Employee().push(self.Tags()[j].TagName()); break;
}
}
}
};
});
This probably look strange and maybe I am doing it unnecessarily complicated.
I use http://aehlke.github.com/tag-it/ as tag manager and it needs an array with TagNames only. Thus I haven´t figured out how to use the Tags()-array directly although I´d like that.
Tasks are presented in an accordion, and I want the Task-tags to be applied in the content panel, while I use my Project-tags as the tagSource for autocomplete-functionality...
But I cannot figure out why my tags are applied 2 times with the ko.computed while they aren´t applied unless I reselect the project without it.
I think you're kind of missing the point of computed observables. The only real distinction between computed observables and a regular function is that you can bind to a computed observable and rely on it to auto-update any time one of its components changes.
The example in the knockout documentation for computed observables uses First/Last name, which is a good example.
Based on this, it's really not a good idea to update the dependency of a computed within that computed itself. In earlier version of knockout this would have actually create an infinite circular reference.
I assume the computed is running twice because Tags and Tasks are both receiving new data, which is triggering an update (but only one update because of the safeguards built into knockout).
A better option would be to subscribe to SelectedProject. Then every time that observable changes you can re-rack all your arrays.
self.SelectedProject.subscribe(function(newValue) {
<load your arrays here>
});

Knockout observable/viewmodel that can be accessed by other viewmodels?

I am building an SPA and everything is going well. It has multiple Viewmodels which are built dynamically and there can be multiple of the same kind, i.e you can open two calculators each having its own model which is bound to a specific div on the page.
Recently I realized that several of the viewmodels were requesting the same data from a web service and on a constant loop every 30 secs - 1 minute. So the same service call was being made multiple times every 30 seconds yet returning the same information.
So what I am trying to figure out is how I can create a "global" observableArray which multiple viewModels can be notified of a change and update rather than doing it themselves, this also helps to make sure the data on the page is consistent.
I was hoping I could do something like:
var GlobalData = (function() {
var commonData = ko.observableArray();
setInterval(function() {...go get data...commonData(data);}, 30000);
return {CommonData:commonData}
})();
ko.applyBindings(GlobalData, $('#RandomLonelyDiv')[0]);
Then later
function Calculator(element){
function init() { ko.applyBindings(calculator, $(element)[0]); }
var calculator = {
CommonData = GlobalData.CommonData
}
return calculator;
}
If it helps the only reason why I dont have a MainViewModel which contains all my other viewmodels is because i frankly dont know how to set that up for my environment.
I have a AppViewModel which contains a ko.observableArray called Windows, which is contains objects which define the options/information to build certain window types.
<!-- ko template:{name:'WindowTemplate', foreach:SelectedTab().Windows} --><!-- /ko -->
and then I have a custom Window binding that creates a modified kendoWindow, which creates a new viewmodel of a specific type such as Calculator, and like I said you could have multiple calculators at one time. But when I started this I wasnt really sure how to put that viewmodel into my AppViewModel. Perhaps its just another array?
It sounds like what you really need is a "Pub/Sub" model. That would allow you to publish and subscribe to messages that are ignorant of their generation or destination. Check out https://github.com/postaljs/postal.js/wiki.
I believe this may be what you are looking for: http://jsfiddle.net/xSKyR/474/
You can subscribe to another viewmodel's observable like so..
var ViewModel1 = function () {
var self = this;
self.something1 = ko.observable("1");
self.clickMe = function (data, event) {
self.something1("2");
};
};
var ViewModel2 = function () {
var self = this;
self.something2 = ko.observable();
vm1.something1.subscribe(function (newValue) {
self.something2(newValue);
});
};
var vm1 = new ViewModel1();
var vm2 = new ViewModel2();
ko.applyBindings(vm1, document.getElementById("vm1"));
ko.applyBindings(vm2, document.getElementById("vm2"));

Synchronize interdependent Dojo widgets/values

I'm about to build a simple "mortgage calculator" where a user is to adjust some sliders OR edit values in input fields in order to calculate some final value based on the provided data.
Schematically it will look something like this:
Slider1 - Input1
Slider2a - Input2a
Slider2b - Input2b
The idea is that the value of the input must be reflected in the slider, and vice versa. In addition, the values and limits of slider 2a/2b and input 2a/2b depend on each other, according to some simple rule.
It has to be done in Dojo, which I've never used before, and, even though Dojo has quite good documentation, it is a little overwhelming, so I'd appreciate if someone could point me in the right direction.
First of all, here is my solution working at jsFiddle: http://jsfiddle.net/phusick/HCx3w/
You can use dojo/aspect, dojo/topic and dojo/Stateful and directly connect those widgets to each other in various ways. You will probably end up with a tightly coupled set of widgets, i.e. those widgets will know about each other, even if there is no reason a particular widget should have any knowledge about the fact its value is being synchronized with another widget.
Contrary to the aforementioned you can apply loose coupling principle, which will allow you to synchronize any number of widgets without any mutual references among them. Here is my solution:
Obtain references to widgets and couple them into sets (arrays):
var slider1 = registry.byId("slider1");
var slider2 = registry.byId("slider2");
var spinner1 = registry.byId("spinner1");
var spinner2 = registry.byId("spinner2");
var set1 = [slider1, spinner1];
var set2 = [slider2, spinner2];
synchronize function:
var synchronize = function(/*Array*/ widgets, /*String*/ topicName) {
var synchronized = function() {
var count = 0;
array.forEach(widgets, function(widget) {
if(widget.get("synchronized") === true) { count++}
});
return (count == widgets.length);
}
array.forEach(widgets, function(w) {
w.set("synchronized", false);
// register onchange handler for each widget in the set
w.on("change", function(value) {
array.forEach(widgets, function(widget) {
if(this !== widget) {
widget.set("value", value);
widget.set("synchronized", true);
}
}, this);
// needed to publish topic just once per value change across all the widgets in the set
if(synchronized()) {
array.forEach(widgets, function(widget) {
widget.set("synchronized", false);
});
// publish topic if any
if(topicName) { topic.publish(topicName, value)};
}
});
});
}
Register sets of widgets to synchronize via sychronize function:
synchronize(set1, "value1-changed"); // synchronize and publish topic when value changes
synchronize(set2); // just synchronize
Subscribe to the topic you registered above:
topic.subscribe("value1-changed", function(value) {
console.log("value1-changed", value);
// here you can change value and limits of of `set2` widgets
});
dojo. Stateful is your friend... http://dojotoolkit.org/reference-guide/1.7/dojo/Stateful.html
Have you tried dojo.connect. This can be used method chaining. So when the event is fired in control multiple methods can be invoked. Beside this there is publish\subscribe mechanism in dojo. In pub\sum model you can write method to subscribe for simple message strings. When some method published that string, than subscriber method will be invoked.

Advice needed... Javascript OOP/namespacing

right now i am at a point where i feel that i need to improve my javascript skills because i already see that what i want to realize will get quite complex. I've iterrated over the same fragment of code now 4 times and i am still not sure if it's the best way.
The task:
A user of a webpage can add different forms to a webpage which i call modules. Each form provides different user inputs and needs to be handled differently. Forms/Modules of the same type can be added to the list of forms as the user likes.
My current solution:
To make the code more readable and seperate functions i use namespaced objects. The first object holds general tasks and refers to the individual forms via a map which holds several arrays where each contains the id of a form and the reference to the object which holds all the functions which need to be performed especially for that kind of form.
The structure looks more or less similar to this:
var module_handler = {
_map : [], /* Map {reference_to_obj, id} */
init: function(){
var module = example_module; /* Predefined for this example */
this.create(module);
},
create: function(module) {
//Store reference to obj id in map
this._map.push([module,id = this.createID()]);
module.create(id);
},
createID: function(id) {
//Recursive function to find an available id
},
remove: function(id) {
//Remove from map
var idx = this._map.indexOf(id);
if(idx!=-1) this._map.splice(idx, 1);
//Remove from DOM
$('#'+id+'').remove();
}
}
var example_module = {
create: function(id) {
//Insert html
$('#'+id+' > .module_edit_inner').replaceWith("<some html>");
}
}
Now comes my question ;-)
Is the idea with the map needed?
I mean: Isn't there something more elegant like:
var moduleXYZ = new example_module(id)
which copies the object and refers only to that form.... Something more logical and making speed improvements?? The main issue is that right now i need to traverse the DOM each time if i call for example "example_module.create() or later on any other function. With this structure i cant refer to the form like with something like "this"???
Do you see any improvements at this point??? This would help me very much!!! Really i am just scared to go the wrong way now looking at all the stuff i will put on top of this ;-)
Thank You!
I think you're looking for prototype:
=========
function exampleModule(id)
{
this.id = id;
}
exampleModule.prototype.create = function()
{
}
=========
var module1 = new exampleModule(123);
module1.create();
var module2 = new exampleModule(456);
module2.create();

Categories