knockout view model to represent a single object - javascript

Edit: This answer here seems to have provided the solution; because I am a lazy sod and was trying to avoid having to define my model in two places (once on server, once on client) I figured there had to be a way. By using the custom binding in the linked solution, I'm able to have the observables created from the various form element data-bind attributes, so basically it builds the model from the form. So it's effectively driving the model definition from the form. I haven't decided yet if this is a bad idea :)
I'm wondering what I'm doing wrong (or indeed, if I even am doing anything wrong). I need to create a form to edit a single record at a time, which has just got some simple text/number properties:
{ItemCode:ABCD,LotNumber:1234,ID:4885,MeasuredValue1:90}
I decided to use ko with the mapping plugin to do it. I'm fairly new to ko.
Anyway I ended up with a view model like this:
var LotModel = function() {
var self = this;
self.Update = function(itemcode,lotnumber) {
var data = { ItemCode: itemcode, LotNumber: lotnumber }
//DoAjax is just a utility function and is working fine.
DoAjax("POST", "LotHistory.aspx/GetLotHistory", data,
function(msg) {
ko.mapping.fromJS(msg.d, null, self);
ko.applyBindings(self);
},
function(xhr, ajaxOptions, thrownError) {
AjaxFailure(xhr, ajaxOptions, thrownError);
}
);
}
}
And later on my code,
var lm = new LotModel();
and finally in $(document).ready
ko.applyBindings(lm);
Now it works, except that if you see in the view model, every time I load data I have to re-call ko.applyBindings(self) in the vm's Update function.
If I comment out that line, it doens't bind. I think that this is because I'm only binding a single object (i.e the view model itself is the object after the ko mapping plugin does its work) but everywhere I read about ko it seems to say "you only need to call this once, ever."
So I can't help feeling I am missing something really obvious; commenting out ko.applyBindings(lm) in the document ready function doesn't make any difference because I automatically call lm.Update in document.ready but commenting it out in the viewmodel breaks it.
So my question is - am I doing this the wrong way? Is it overkill for just a single object at a time type binding? I mean it doesn't bother me too much, it works as I want it to but still, it's nagging at me...

It's indeed best not to reapply bindings many times if avoidable. The problem is that you don't have any observable properties in your viewmodel to begin with. An initial call to ko.mapping.fromJS can fix this (or you can manually add the observables) e.g.:
ko.mapping.fromJS({
ItemCode: '', LotNumber: 0, ID: 0, MeasuredValue1: 0
}, null, self);
See fiddle for a working example: http://jsfiddle.net/antishok/qpwqH/1/

Related

knockout.js, breeze and dynatree/fancytree

This is really tricky to get my head around as I'm not used to this style of programming/data management.
All I'm trying to do at the moment is pass a json object returned via breeze into a dynatree or fancytree.
The examples that exist online all assume that the tree will do the ajax call via "initajax" or that some weirdly convoluted custom binding handler is needed into which various objects are passed:
ko.bindingHandlers.dynatree = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
setTimeout(function () {
$(element).dynatree({
noLink: true, // noLink is required to 'unlock' the checkboxes
minExpandLevel: 2
})
// the timeout value shows the correct knockout bindings BEFORE dynatree kicks in.
}, 1000);
}
}
This all seems too complicated to me, surely? I already have the json object, I know that's working. If I use knockout to "foreach" bind it to some plain html then all data is displayed just fine. In my mind all I need to do is initialize the tree div and pass it the json object... It's just that I have no idea how to do that!
I've tried using the jsfiddle here: http://jsfiddle.net/Ebram/UhA3m/5/ but chrome developer tools complain about the element having no "dynatree" method when the custom binding handler fires. It's passing in a "ul" element and that could be the problem - surely it should be passing in the tree div, not the ul element?
Anyhow, if anyone could point me in the right direction I'd hugely appreciate it. As I'm using John Papa's SPA methodology, I'm also unsure as to where I would put any separate js initialization code as the underlying viewmodel isn't the right place for me to be doing a $(#tree).dynatree initialization type call, is it? I must admit I've not got my head around this yet.
I suppose all I'm looking for is something along the lines of "once the viewmodel for this view has finished loading and the knockout binding is being done, initialize the dynatree div and pass this json object to the tree" if that makes sense in pseudocode?
I can hopefully point you in the approximate right direction.
It seems dynatree can also take JSON from a file as well as an AJAX request. In this example Lazy Loading, if you look in the source code, there's:
// In real life we would call a URL on the server like this:
...
// .. but here we use a local file instead:
Storing your data in a file to get it in seems awfully wasteful. Now that we know it's a little flexible in what it gets, let's see where it uses the data and maybe we can get it to use a local variable instead. let see where it loads it
Looking in the dynatree source, there's a function associated with appendAjax. (line 1774 in my source.) A little short on time at the moment, but I'd find where it gets the JSON and what it does with it. Perhaps you can do the same thing outside or mod the handling of ajaxOptions to take a variable with the JSON.

How to remove the "name" param in for fields in ExtJS 4

I am integrating a payment provider into a ExtJS websites.
Basically, a form needs to be created and the form fields is send to the payment provider using Ajax.
The problem is that the payment provider does not allow that the form fields has a "name" param assigned to the "" tag. They do a manual check of the implementation and makes sure it is not there.
I assume it is a counter-mesasure for when the visitor has Ajax dissabled and the form gets submitted to my server instead, revealing the credit card. I know it does not make any sense with ExtJS, as it would not work without Javascript turned on, but non-the-less, that is the rule from the payment provider.
So, how can I force ExtJS to not put a "name" param in the form field? I have tried putting "name: ''" into the fields, but that gets ignored.
Do I use the template-system in ExtJS to solve this?
So Eric is perfectly right that it can be done much easier then modifying the whole template but non the less I would use a plugin for such a special case. I made a quick one:
Ext.define('Ext.form.field.plugin.NoNameAttribute', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.nonameattribute',
init: function(cmp) {
Ext.Function.interceptAfterCust(cmp, "getSubTplData", function(data){
delete data['name'];
return data;
});
}
});
Note the used method interceptAfterCust is a custom one of mine that modify the existing one by handing the result of the original to the intercepting one as argument. It is also using the given original object (which can be threaten as a scope) as scope for the original method. The easiest would be to add these method to Ext.Function
Ext.Function.interceptAfterCust = function(object, methodName, fn, scope) {
var method = object[methodName] || Ext.emptyFn;
return (object[methodName] = function() {
return fn.call(scope || this, method.apply(object, arguments));
});
}
Here is a working JSFiddle where the first field will not have a name attribute on the dom even if it exist in the component.
There's a surprisingly simple solution to this. I tested it with Ext.form.field.Text and Ext.form.field.ComboBox and it works well, but I don't know if it works for all form fields or if there are any negative side-effects. Use with caution.
Ext.define('Override.form.field.Base', {
override: 'Ext.form.field.Base',
getSubTplData: function(){
var data = this.callParent(arguments);
delete data.name;
return data;
}
});
Basically, it removes the auto-generated name from the render data before passing it along. The best part is that no private methods are involved so this should be a stable solution.
I prefer this in the field config options:
submitValue: false
Available since ExtJS 3.4

Structuring my jquery/javascript properly

These days I find myself putting a lot of my code in the $(document).ready() which does not seem clean to me. For example, if I am creating something that will query my database via ajax and return it and append it to my list i would do something like this:
$(function(){
//Initialize my DOM elements
$MyList = $("#MyList"),
$MyButton = $("#MyButton");
//Add my Click event
$MyButton.click(function(){
$.ajax({
type: 'POST',
url: "/lists/getmylist",
contentType: "application/json",
success: function(results){
//Parse my results and append them using my favorite templating helper
var jsonResults = $.parseJSON(result);
$MyList.mustache("my-template", jsonResults);
}
});
})
});
Now I know this is a small example but it starts to get really big and messy when I have multiple click events, ajax requests etc. It all ends up going in my document ready. I know that I can possibly put all my ajax requests in an external javascript file to help make it cleaner, but is this architecture in general ok? just seems like its really messy. I have seen others use plugin architectures or init functions. I usually have this document ready at the bottom of all my pages and just throw in whatever is necessary to make my page work correctly. Is this a good way to structure my js?
I think the addition of some Model objects and general object oriented programming principals might go a long way here. If you break your your data fetching and storing out into model classes it should help a lot.
Here are some links that should get you started thinking about OO with Javascript.
Writing Object-Oriented JavaScript
Javascript Design Patterns
Javascript: prototypal inheritance
Javascript: prototypal inheritance 2
Another thing that might help out would be to break the Javascript into multiple files. One for global scripts that might be included via a header that attaches to all your pages and a script for each of your pages that requires it.
Perhaps Backbone.js ( or one of the other frameworks ) could be part of the rescue you are looking for.
I found Backbone immensely helpful organising some inherited spaghetti. Your starting point might be to transition your massive document ready into a backbone view (or multiples of)
Organise your scripts by separating out the views, collections, models into individual files then bundle and minify them together into a single file so the browser only needs to make one request instead of many.
ASP.NET MVC4 can do the bundling for you, it also works similarly on MVC3
This is just a example of simple starting point, there are more advanced techniques (eg. AMD, require.js) to reduce the script size per page, but with caching and gzip I find that the single everything script bundle is fine for a lot of cases.
As for your example, here's a possible backbone implementation
Remember to namespace out your code...
var app = app || {};
$(function ($) {
// depending on your server setup you might be able to just override the url
// and get away with what you want. Otherwise you might look into overriding
// the save/fetch or sync methods of the collection
app.MyListCollection = Backbone.Collection.extend({
url: '/lists/getmylist'
});
app.MyListView = Backbone.View.extend({
//bind the view to the existing element in the HTML.
el: '#MyList',
// your mustache template
template:$('#list-template').html(),
// hook up your event handlers to page components
//(within the context of your el)
events: {
'click #MyButton': 'onMyButtonClick'
},
//called on object creation.
initialize: function () {
//you could create a collection here or pass it into the init function
this.collection = new app.MyListCollection();
//when the collection is changes, call the render method
this.listenTo(this.collection, 'reset', this.render);
},
// render is named by convention, but its where you would "redraw" your view and apply your template
render: function () {
this.$el.html(
Mustache.render(
this.template(this.collection.toJSON())));
return this;
},
//your click handler
onMyButtonClick: function(e){
this.collection.fetch();
}
});
});
use your doc ready to spin up whatever backbone functionality you need
and use it bootstrap your javascript with any server side data that you may have.
$(function () {
// create an instance of your view
new app.MyListView();
//example bootstrap using razor
app.title = #Model.Title;
});

How to setup validation for my knockout observables, that i created using Mappings plugin

have been trying to set up validation like (required, min length, max lenght etc) for the properties on my knockout observables. I created my observables using Mapping plugin. I found the followign links, but could someone help me, figure out which is the right way to go, to set up validation. Here are the links that i was looking at.
http://knockoutjs.com/documentation/extenders.html
I put my code in jsfiddle, Currently it is not running, since it is complaining about knockout files. But Thought some one can see my code here http://jsfiddle.net/wQfuM/13/
I couldn't get your jsfiddle to work. but here's a simple way to do it. You can control the creation of the observables using the mapping plugins mapping options object.
var json = {
someValue: "foo"
};
var viewModel = function(data) {
ko.mapping.fromJS(data, {
someValue: {
create: function(options) {
return ko.observable(options.data).extend({ minLength: 10 });
}
}
}, this);
};
ko.applyBindings(new viewModel(json));
http://jsfiddle.net/madcapnmckay/b3UrF/1/
Hope this helps.

Loading data with dependentObservable causing an infinite loop

I'm playing around with Knockout and now trying to use the Knockout address plugin (based on jQuery address).
This code below works, except that when I try entering the address the linkObservableToUrl provides the page is loaded without the right tags. I guess something is wrong in the way I'm loading the messages, but I'm not sure how this should be done using the Knockout framework.
I've got the following code, which is causing an infinite loop:
var viewModel = {
page: ko.observable(1),
//messages: ko.observableArray([]),
tags: ko.observable()
};
viewModel.filterTags = function (filterTags) {
viewModel.tags(filterTags);
};
viewModel.messages = ko.dependentObservable(function () {
$.ajax(
// abbreviated
data: ko.toJSON(viewModel),
// abbreviated
)}, viewModel);
ko.applyBindings(viewModel);
ko.linkObservableToUrl(viewModel.tags, "tags", null);
How can I solve this and still have the messages depend on page and tags?
Switch to AngularJS. Angular's databinding is much better than Knockout's. Much of the problems you are encountering here with infinite loops, etc. are due to Knockout's need for observable wrappers.
Angular does not require observable wrappers of your objects. Angular can observe any standard JSON/Javascript object, and databind directly to any HTML element via MVVM.
In Angular, you would simply make your AJAX call for ViewModel.messages, and the standard JSON would be applied to your ViewModel.messages property. No observable wrappers. This eliminates the need for ko.dependentObservable() and thus - removes your infinite loop.
http://www.angularjs.org
In the second example (which is quit long for a code snippet) you have this:
viewModel.messages = ko.dependentObservable(function () {
...
data: ko.toJSON(viewModel),
...
If the call to ko.toJSON tries to get the value of all the observable properties on the view model, it will try to evaluate the viewModel.messages property. That will call ko.toJSON again, leading to an infinite loop.

Categories

Resources