I'm trying to build an complexe view with knockout.js and have a few problems..
The content data for the view model is loaded over ajax as JSON. The JSON is quite complex and has multiple nested objects from which some should be observable and others not.
Here is a little example (real one is some levels deeper)
{
BaseData:{
Title:'BaseDataTitle',
DataArray:[{Title:'obs1'}],//this should be observable
SecondArray:[{Title:'notobs1'}],//this should not be observable
},
DataArray:[{Title:'obs1'}]
}
http://jsfiddle.net/wPs7e/
is there any possibility to do that with knockout?
thanks for your help!
You'll have to do some work with your objects...you could use the mapping plug-in, however you'll still need to create some objects to receive your data and conditionally apply the bindings...
var KOObject = function(rawdata) {
var self = this;
self.BaseData = new BaseDataObject(rawdata.BaseData);
self.DataArray = rawdata.DataArray;
}
var BaseDataObject = function(baseData) {
var self = this;
self.Title = baseData.Title;
// you could keep chaining the creation of objects with this concept
self.DataArray = ko.observableArray(baseData.DataArray);
self.SecondArray = ko.observableArray(baseData.SecondArray);
}
before you apply your bindings, new up this object
ko.applyBindings(new KOObject(rawdata));
The mapping plugin is a great idea for automating both the serialization and deserialization of data with knockout...it's just not always necessary if you have the option of initializing your data sets manually...it's good practice anyways.
Related
I am creating an app in Xamarin and am having issues querying a general JSON document from my CosmosDB. I am able to query my DB with a known structure (very similar to what we see in the Xamarin ToDo Example):
public async static Task<List<ToDoItem>> GetToDoItems()
{
var todos = new List<ToDoItem>();
if (!await Initialize())
return todos;
**var todoQuery = docClient.CreateDocumentQuery<ToDoItem>(
UriFactory.CreateDocumentCollectionUri(databaseName, collectionName),
new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true })
.Where(todo => todo.Completed == false)
.AsDocumentQuery();**
while (todoQuery.HasMoreResults)
{
var queryResults = await todoQuery.ExecuteNextAsync<ToDoItem>();
todos.AddRange(queryResults);
}
return todos;
}
The problem I see with this "code fixed scheme" approach is that if the scheme of your JSON file changes throughout development, older versions of code will overwrite the newer scheme in CosmosDB since writes to the DB are on a document level and not a property level. Instead, it would be helpful for older versions of the code to be able to pull down the latest scheme and work with the properties that it knows about without having to force the user to update.
Does anyone know how to query a schema-less JSON document out of CosmosDB? Thanks!
Use JObject as the type to query a schema-less JSON document out of CosmosDB, and use following code to pull the raw JSON data into your object:
JsonConvert.DeserializeObject<Class>(JSONString);
I have a rest service for which I am sending the Json data as ["1","2","3"](list of strings) which is working fine in firefox rest client plugin, but while sending the data in application the structure is {"0":"1","1":"2","2":"3"} format, and I am not able to pass the data, how to convert the {"0":"1","1":"2","2":"3"} to ["1","2","3"] so that I can send the data through application, any help would be greatly appreciated.
If the format of the json is { "index" : "value" }, is what I'm seeing in {"0":"1","1":"2","2":"3"}, then we can take advantage of that information and you can do this:
var myObj = {"0":"1","1":"2","2":"3"};
var convertToList = function(object) {
var i = 0;
var list = [];
while(object.hasOwnProperty(i)) { // check if value exists for index i
list.push(object[i]); // add value into list
i++; // increment index
}
return list;
};
var result = convertToList(myObj); // result: ["1", "2", "3"]
See fiddle: http://jsfiddle.net/amyamy86/NzudC/
Use a fake index to "iterate" through the list. Keep in mind that this won't work if there is a break in the indices, can't be this: {"0":"1","2":"3"}
You need to parse out the json back into a javascript object. There are parsing tools in the later iterations of dojo as one of the other contributors already pointed out, however most browsers support JSON.parse(), which is defined in ECMA-262 5th Edition (the specification that JS is based on). Its usage is:
var str = your_incoming_json_string,
// here is the line ...
obj = JSON.parse(string);
// DEBUG: pump it out to console to see what it looks like
a.forEach(function(entry) {
console.log(entry);
});
For the browsers that don't support JSON.parse() you can implement it using json2.js, but since you are actually using dojo, then dojo.fromJson() is your way to go. Dojo takes care of browser independence for you.
var str = your_incoming_json_string,
// here is the line ...
obj = dojo.fromJson(str);
// DEBUG: pump it out to console to see what it looks like
a.forEach(function(entry) {
console.log(entry);
});
If you're using an AMD version of Dojo then you will need to go back to the Dojo documentation and look at dojo/_base/json examples on the dojo.fromJson page.
I can implement Mvvm with Knockout.js. But I want to use it with cross browser(FF and Chrome) supported Html 5 offline storage.
I want to bind html objects to offline storage.
I haven't tried it, but there is a knockout.localStorage project on GitHub, that seems to be what you are looking for.
With that plugin, you should be able to pass an object as a second argument, when you create your observable, which saves the observable into localStorage.
From the documentation:
var viewModel = {
name: ko.observable('James', {persist: 'name'})
}
ko.applyBindings(viewModel);
You can use a library such as amplify.js which can serialize objects to localStorage (cross browser). It falls back to older storage tools for older browsers too. First, unwrap the observables to a JSON object, then use amplify.store to serialize the object and store it. Then you can pull it back out and map it back to an observable object when you want to fetch it.
http://amplifyjs.com/api/store/
http://craigcav.wordpress.com/2012/05/16/simple-client-storage-for-view-models-with-amplifyjs-and-knockout/
His solution works!
I worked out a solution bases on the subscribe feature of KnockoutJS. It takes a model and persist all the observable properties.
ko.persistChanges = function (vm, prefix) {
if (prefix === undefined) {
prefix = '';
}
for (var n in vm) {
var observable = vm[n];
var key = prefix + n;
if (ko.isObservable(observable) && !ko.isComputed(observable)) {
//track change of observable
ko.trackChange(observable, key);
//force load
observable();
}
}
};
Check http://keestalkstech.com/2014/02/automatic-knockout-model-persistence-offline-with-amplify/ for code and JSFiddle example.
I have just started getting familiar with dojo and creating widgets and have a web UI which I would like to now populate with data. My question is merely to get some references or ideas on how to do this. My databases are all sql server 2008 and I usually work with microsoft.net. I thought that I would probably have to create a service that calls the sql queries and converts the results into json and feed that into the widgets whether it be the datagrid or charts. Just not sure how to do this and if it is indeed possible to do that. Any ideas appreciated.
EDIT:
store = new dojo.data.ItemFileWriteStore({
url: "hof-batting.json"
});
ngrid = new dojox.grid.DataGrid({
store: store,
id: 'ngrid',
structure: [
{ name: "Search Term", field: "searchterm", width: "10%" },
{ name: "Import Date", field: "importDate", width: "10%" }
]
}, "grid");
ngrid.startup();
I want to add data returned from my web service to this datagrid and use the same principle to add data to a chart.
Your describe exactly what you need to do.
We use C# to query our database to get the data and then convert it to json. We use multiple techniques right now for json serialization. I would recommend using JSON.NET. It is what the .NET MVC team is going to use. I would not use the DataContractSerialization that is currently part of .NET.
http://json.codeplex.com/
We sometimes put JSON right on the page and the javascript accesses it as a page variable. Other times we call services in .NET. We use WCF and we have also used an .ashx file to give the web client json data.
The structure of the json will be the contract between your dojo widgets and web server. I would use what the chart widgets or store will need to start the process of defining the contract.
EDIT
WCF Interface
[OperationContract]
[WebInvoke(Method="POST", UriTemplate = "/data/{service}/",
BodyStyle = WebMessageBodyStyle.WrappedRequest)]
String RetrieveData(string service, Stream streamdata);
The implementation returns a string that is the json. This gets sent to the browser as json, but it's wrapped by .NET by an xml node. I have a utility function that cleans it.
MyUtil._xmlPrefix =
'<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">';
MyUtil._xmlPostfix = '</string>';
MyUtil.CleanJsonResponse = function(data) {
// summary:
// a method that cleans a .NET response and converts it
// to a javascript object with the JSON.
// The .NET framework, doesn't easily allow for custom serialization,
// so the results are shipped as a string and we need to remove the
// crap that Microsoft adds to the response.
var d = data;
if (d.startsWith(MyUtil._xmlPrefix)) {
d = d.substring(MyUtil._xmlPrefix.length);
}
if (d.endsWith(MyUtil._xmlPostfix)) {
d = d.substring(0, d.length - MyUtil._xmlPostfix.length);
}
return dojo.fromJson(d);
};
// utility methods I have added to string
String.prototype.startsWith = function(str) {
return this.slice(0, str.length) == str;
};
String.prototype.endsWith = function(str) {
return this.slice(-str.length) == str;
};
I would like to know if any better way to create multiple collections fetching from a single big JSON file. I got a JSON file looks like this.
{
"Languages": [...],
"ProductTypes": [...],
"Menus": [...],
"Submenus": [...],
"SampleOne": [...],
"SampleTwo": [...],
"SampleMore": [...]
}
I am using the url/fetch to create each collection for each node of the JSON above.
var source = 'data/sample.json';
Languages.url = source;
Languages.fetch();
ProductTypes.url = source;
ProductTypes.fetch();
Menus.url = source;
Menus.fetch();
Submenus.url = source;
Submenus.fetch();
SampleOne.url = source;
SampleOne.fetch();
SampleTwo.url = source;
SampleTwo.fetch();
SampleMore.url = source;
SampleMore.fetch();
Any better solution for this?
Backbone is great for when your application fits the mold it provides. But don't be afraid to go around it when it makes sense for your application. It's a very small library. Making repetitive and duplicate GET requests just to fit backbone's mold is probably prohibitively inefficient. Check out jQuery.getJSON or your favorite basic AJAX library, paired with some basic metaprogramming as following:
//Put your real collection constructors here. Just examples.
var collections = {
Languages: Backbone.Collection.extend(),
ProductTypes: Backbone.Collection.extend(),
Menus: Backbone.Collection.extend()
};
function fetch() {
$.getJSON("/url/to/your/big.json", {
success: function (response) {
for (var name in collections) {
//Grab the list of raw json objects by name out of the response
//pass it to your collection's constructor
//and store a reference to your now-populated collection instance
//in your collection lookup object
collections[name] = new collections[name](response[name]);
}
}
});
}
fetch();
Once you've called fetch() and the asyn callback has completed, you can do things like collections.Menus.at(0) to get at the loaded model instances.
Your current approach, in addition to being pretty long, risks retrieving the large file multiple times (browser caching won't always work here, especially if the first request hasn't completed by the time you make the next one).
I think the easiest option here is to go with straight jQuery, rather than Backbone, then use .reset() on your collections:
$.get('data/sample.json', function(data) {
Languages.reset(data['Languages']);
ProductTypes.reset(data['ProductTypes']);
// etc
});
If you wanted to cut down on the redundant code, you can put your collections into a namespace like app and then do something like this (though it might be a bit too clever to be legible):
app.Languages = new LanguageCollection();
// etc
$.get('data/sample.json', function(data) {
_(['Languages', 'ProductTypes', ... ]).each(function(collection) {
app[collection].reset(data[collection]);
})
});
I think you can solve your need and still stay into the Backbone paradigm, I think an elegant solution that fits to me is create a Model that fetch the big JSON and uses it to fetch all the Collections in its change event:
var App = Backbone.Model.extend({
url: "http://myserver.com/data/sample.json",
initialize: function( opts ){
this.languages = new Languages();
this.productTypes = new ProductTypes();
// ...
this.on( "change", this.fetchCollections, this );
},
fetchCollections: function(){
this.languages.reset( this.get( "Languages" ) );
this.productTypes.reset( this.get( "ProductTypes" ) );
// ...
}
});
var myApp = new App();
myApp.fetch();
You have access to all your collections through:
myApp.languages
myApp.productTypes
...
You can easily do this with a parse method. Set up a model and create an attribute for each collection. There's nothing saying your model attribute has to be a single piece of data and can't be a collection.
When you run your fetch it will return back the entire response to a parse method that you can override by creating a parse function in your model. Something like:
parse: function(response) {
var myResponse = {};
_.each(response.data, function(value, key) {
myResponse[key] = new Backbone.Collection(value);
}
return myResponse;
}
You could also create new collections at a global level or into some other namespace if you'd rather not have them contained in a model, but that's up to you.
To get them from the model later you'd just have to do something like:
model.get('Languages');
backbone-relational provides a solution within backbone (without using jQuery.getJSON) which might make sense if you're already using it. Short answer at https://stackoverflow.com/a/11095675/70987 which I'd be happy to elaborate on if needed.