knockout.js load form into viewModel - asp.net-mvc-2

I'm currently successfully using knockout.js to handle all of my data-binding in my application. However, on each page load, in my document.ready I'm doing an initial asnychronous data load like this:
$(document).ready() {
getData()
});
However, is it possible to instead, load the data into a form (using ASP.NET MVC2) and then reverse load the data into the view model based on the data-bind tags?
I feel like this doesn't work, I just want to confirm that I'm not doing anything improperly.

The 'value' binding initially sets the value of the element to the one in your view model, so no. However, you could probably duplicate the code for the 'value' binding into your own handler that does initially set the model values from the values on the controls. Download the debug version of knockout, and look for ko.bindingHandlers['value'] = { on line 2182. Copy this binding handler declaration and change 'value' to something else, then add a call to valueUpdateHandler() at the end of init:
ko.bindingHandlers['myvalue'] = {
'init': function (element, valueAccessor, allBindingsAccessor) {
// skipping code
valueUpdateHandler(); // update model with control values
},
'update': function (element, valueAccessor) {
// skipping code
}
};
Now when you use the myvalue binding, your model will be updated with the control values when initially bound:
<input type="text" data-bind="myvalue: name"></input>
Alternatively you could call the original values instead of copying all the code, and just add the code from valueUpdateHandler after the init:
ko.bindingHandlers['myvalue'] = {
'init': function (element, valueAccessor, allBindingsAccessor) {
// call existing value init code
ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);
// valueUpdateHandler() code
var modelValue = valueAccessor();
var elementValue = ko.selectExtensions.readValue(element);
ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue, /* checkIfDifferent: */ true);
},
'update': function (element, valueAccessor) {
// call existing value update code
ko.bindingHandlers['value'].update(element, valueAccessor);
}
};
If you don't want to use AJAX, you can always get the values into javascript by serializing your model as JSON (razor syntax):
<script type="text/javascript">
var model = #(new HtmlString(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model)));
</script>

There's a working example here (not mine) with several different ways to achieve this:
http://jsfiddle.net/rniemeyer/5Z2SC/

Related

How can customData can be binded with JavaScript

In the affected application is a responsive table whose ColumnListItems are added via JavaScript code. Now the lines should be highlighted by the highlighting mechanism depending on their state. The first idea was to control the whole thing via a normal controller function. I quickly discarded the idea, since the formatter is intended for such cases. So I created the appropriate Formatter function and referenced it in the JavaScript code. The call seems to work without errors, because the "console.log" is triggered in each case. Also the transfer of fixed values is possible without problems. However, the values I would have to transfer are located within customData of each line...
No matter how I try to form the path I get an "undefined" or "null" output.
I have already tried the following paths:
"/edited"
"/customData/edited"
"mAggregations/customData/0/mProperties/value"
"/mAggregations/items/0/mAggregations/customData/0/mProperties/value"
The code from Controller.js (with consciously differently indicated paths):
var colListItem = new sap.m.ColumnListItem({
highlight: {
parts: [{
path: "/mAggregations/items/0/mAggregations/customData/0/mProperties/value"
}, {
path: "/edited"
}],
formatter: Formatter.setIndication
},
cells: [oItems]
});
// first parameter to pass while runtime to the formatter
colListItem.data("editable", false);
// second paramter for the formatter function
colListItem.data("edited", false);
oTable.addItem(colListItem);
The code from Formatter.js:
setIndication: function (bEditable, bEdited) {
var sReturn;
if (bEditable && bEdited) {
// list item is in edit mode and edited
sReturn = "Error";
} else if (bEditable || bEdited) {
// list item is in edit mode or edited
sReturn = "Success";
} else {
sReturn = "None";
}
return sReturn;
}
The goal would also be for the formatter to automatically use the value of the model in order to avoid its own implementation of a listener, etc.
I hope one of you has a good/new idea that might bring me a solution :)
Many thanks in advance!
You cannot bind against the customData. Because the customData is located in the element, it is like a property.
Thats why you defined it here on colListItem: colListItem.data("key", value)
You only can bind against a model.
So I see three solutions
Store the information in a separate local JSON model whereof you can speficy your binding path to supply the values to your formatter
Do not supply the information via a binding path to the formatter, but read a model/object/array from a global variable in the controller holding the information via this (=controller) in formatter function
Store the information in the customData of each element and access the element reference in the formatter function via this(=ColumnListItem).data().
Passing the context to the formatter similar to this formatter: [Formatter.setIndication, colListItem]
Cons of 1. and 2: you need a key for a respective lookup in the other model or object.
From what I understand I would solve it with solution 3.

Ext.define() order

I'm using Extjs5 and Sencha Cmd, and I'm working on a l10n engine (over gettext) to implement localization.
Suppose I want to offer a translation function to every class of my project, named _().
In every controller, view, model and any class, I'd like to be able to write something like that:
Ext.define('FooClass', {
someStrings: [
_('One string to translate'),
_('A second string to translate'),
_('Yet another string to translate')
]
});
First problem: _() must exist before all the Ext.define() of my project are executed. How to achieve that?
Second problem: _() is looking in "catalogs" that are some JavaScript files generated from .po files (gettext). So, those catalogs must have been loaded, before all the Ext.define() of my app are executed.
_() is a synchronous function, it musts immediately return the translated string.
Edit concerning the edited question
You have at least two ways to load External libraries:
Ext.Loader.loadScript
loadScript( options )
Loads the specified script URL and calls the supplied callbacks. If
this method is called before Ext.isReady, the script's load will delay
the transition to ready. This can be used to load arbitrary scripts
that may contain further Ext.require calls.
Parameters
options : Object/String/String[] //The options object or simply the URL(s) to load.
// options params:
url : String //The URL from which to load the script.
onLoad : Function (optional) //The callback to call on successful load.
onError : Function (optional) //The callback to call on failure to load.
scope : Object (optional) //The scope (this) for the supplied callbacks.
If you still run into problems you can force the loader to do a sync loading:
syncLoadScripts: function(options) {
var Loader = Ext.Loader,
syncwas = Loader.syncModeEnabled;
Loader.syncModeEnabled = true;
Loader.loadScripts(options);
Loader.syncModeEnabled = syncwas;
}
Place this in a file right after the ExtJS library and before the generated app.js.
Old Answer
You need to require a class when it is needed, that should solve your problems. If you don't require sencha command/the ExtJS class system cannot know that you need a specific class.
Ext.define('Class1', {
requires: ['Class2'],
items: [
{
xtype: 'combo',
fieldLabel: Class2.method('This is a field label')
}
]
});
For further reading take a look at:
requires
requires : String[]
List of classes that have to be loaded before instantiating this
class. For example:
Ext.define('Mother', {
requires: ['Child'],
giveBirth: function() {
// we can be sure that child class is available.
return new Child();
}
});
uses
uses : String[]
List of optional classes to load together with this class. These
aren't neccessarily loaded before this class is created, but are
guaranteed to be available before Ext.onReady listeners are invoked.
For example:
Ext.define('Mother', {
uses: ['Child'],
giveBirth: function() {
// This code might, or might not work:
// return new Child();
// Instead use Ext.create() to load the class at the spot if not loaded already:
return Ext.create('Child');
}
});
Define the translate function outside the scope of the ExtJs project and include it before the Ext application is included in the index.html.
The scripts are loaded in the right order and the _() function is ready to use in your whole project.
i18n.js
function _() {
// do the translation
}
index.html
<html>
<head>
<script src="i18n.js"></script>
<script id="microloader" type="text/javascript" src="bootstrap.js"></script>
</head>
<body>
</body>
</html>

Restangular extendModel on new object

Restangular offers a feature, extendModel, which lets you add functionality onto objects returned from the server. Is there any way to get these methods added to an empty / new model, that hasn't yet been saved to the server?
I wanted to do the same thing but didn't find an example. Here's how I ended up doing it:
models.factory('User', function(Restangular) {
var route = 'users';
var init = {a:1, b:2}; // custom User properties
Restangular.extendModel(route, function(model) {
// User functions
model.myfunc = function() {...}
return model;
});
var User = Restangular.all(route);
User.create = function(obj) {
// init provides default values which will be overridden by obj
return Restangular.restangularizeElement(null, _.merge({}, init, obj), route);
}
return User;
}
Some things to be aware of:
Use a function like _.merge() instead of angular.extend() because it clones the init variable rather than simply assigning its properties.
There is a known issue with Restangular 1.x that causes the Element's bound data to not be updated when you modify its properties (see #367 and related). The workaround is to call restangularizeElement() again before calling save(). However this call will always set fromServer to false which causes a POST to be sent so I wrote a wrapper function that checks if id is non-null and sets fromServer to true.

Is there a way to "watch" a variable in google chrome?

Basically, I want to add a breakpoint every time a given closure variable is changed. Is there any way to do this?
I don't think there's currently a way to directly watch variables, but if you can put the closure variable in an object, then you can use Object.observe() to observe that object for changes. (Object.observe can only observe objects)
This requires you to have Experimental Javascript enabled - chrome://flags/#enable-javascript-harmony.
(function(){
var holder = {
watchedVariable: "something"
};
Object.observe(holder, function (changes) {
// returns an array of objects(changes)
if ( changes[0].name === "watchedVariable" ) {
debugger;
}
});
})()

Default values for missing parameters

I'm using Knockout with jQuery and jQuery templates. Assume that I have a template which expects a person object
<script type="text/html" id="person_template">
<tr><td>Forename</td><td><input type="textbox" data-bind="value:FORENAME" /></td></tr>
<tr><td>Surname</td><td><input type="textbox" data-bind="value: SURNAME"/></td></tr>
</script>
Now, if I pass an object with just a FORENAME to this template, I will get an error:
SURNAME is not defined error
I tried to create a custom binding in Knockout, but the error is thrown before it even gets there.
If I fill in these empty fields before passing the object to the template, I know everything will work out, but I would like to have the solution in my template rather than in my javascript.
Does anyone know a method that might help for situations like these?
This is a bit challenging, because you are within a template. While preparing the template, KO accesses the variable (well, actually it is accessed in jQuery Templates by a function that KO built).
One option is to pass your property as a string to a custom binding and make sure that it is initialized.
It would be like:
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var value = valueAccessor();
if (!context[value]) {
context[value] = ko.observable();
}
var realValueAccessor = function() {
return context[value];
}
//call the real value binding
ko.bindingHandlers.value.init(element, realValueAccessor, allBindingsAccessor, context);
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
var realValueAccessor = function() {
return context[valueAccessor()];
}
//call the real value binding
ko.bindingHandlers.value.update(element, realValueAccessor);
}
}
So, this would validate that your object has the field, if it does not it creates a new observable for that field. Then, it hands it off to the real value binding.
A very similar (but less verbose) alternative to this would be to have the binding ensure that the field is there and then rewrite the binding attribute to use the real value binding. Something like:
//Another option: rewrite binding after making sure that it is initialized
ko.bindingHandlers.valueWithInit = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var value = valueAccessor();
if (!context[value]) {
context[value] = ko.observable();
}
$(element).attr("data-bind", "value: " + value);
ko.applyBindings(context, element);
}
}
Both of these assume that the field that you are passing is directly off of the object that is the context of your template (so, it wouldn't work if you passed something with global scope like 'viewModel.someProperty').
Here is a working sample with both options: http://jsfiddle.net/rniemeyer/dFSeB/
I would rather not pass the field as a string, but there is not really a good way around it that I see.
You'll be better off ensuring that the object passed to the template has all the parameters set in. If they are not then you can add default values but putting all this logic in the template is going against the MVVM pattern. The templates (like Views in mvc) are not supposed to contain any logic IMO.