Binding viewmodel with knockoutjs mapping - json

I've just decided to learn knockoutjs, and I'm having a bit of an issue binding some json to my viewmodel. I've searched heaps on it, tried heaps of things, but I must have missed something.
Javascript:
var data = {
"TestList": [{ "ID": "1", "Name": "Dave" }, { "ID": "2", "Name": "Mustaine" }],
"TestText": "Hello World"
};
var viewModel = {};
ko.mapping.fromJSON(data, viewModel);
ko.applyBindings(viewModel);
HTML
TestText: <span data-bind="text: TestText"></span><br>
TestList: <select id="TestList"
data-bind="
options: TestList,
optionsText: 'Name',
optionsValue: 'ID',
optionsCaption: 'Please Select'"></select>
EDIT
the variable 'data' was used as an example of the json I get back from the server. Anyways, I've updated the above code with getJSON and getting an error which the above example really can't give me as it doesn't use getJSON.
Updated JAVASCRIPT:
var viewModel;
$.getJSON('/myurl',
function (data) {
viewModel = data;
});
alert(viewModel);
$(function() {
ko.applyBindings(viewModel);
});
The issue i'm having here is that it works.. as long as the alert box is there. If i comment that line out, it doesn't work!

Your first issue is that you are declaring data as an array with only one member in it, but you are not referring to this array within your data-bind declarations.
Secondly if you have data as a JavaScript object you do not need fromJSON on it.
Updated JS Code:
var data = {
"TestList": [{ "ID": "1", "Name": "Dave" }, { "ID": "2", "Name": "Mustaine" }],
"TestText": "Hello World"
};
var viewModel = data;//{};
ko.applyBindings(viewModel);
Working fiddle: http://jsfiddle.net/AfgAG/19/
EDIT: Updated answer to reflect the updates in the question and also the initial answer.\
Your view model needs to have the options list as an observable array for the options binding to work.
Also, it is better to start off your view model with structure defined and the observables defined for the data binding to work when you update with your AJAX call.
See below for updated javascript code. Could not create a fiddle as I was unable to access the fiddle side.
var viewModel =
{
TestText: ko.observable('My Initial Text'),
TestList: ko.observableArray([])
}
ko.applyBindings(viewModel);
// using set time out here to simulate your ajax call.
setTimeout(function () {
// this would normally be the content for your getJson success method.
// this is where you use your from JSON.
// data is a javascript object from ajax response.
var data = {
"TestList": [{ "ID": "1", "Name": "Dave" }, { "ID": "2", "Name": "Mustaine" }],
"TestText": "Hello World"
};
// update the view model observable properties.
viewModel.TestText(data.TestText);
viewModel.TestList(data.TestList);
}, 2000);

I've figured it out. FYI, This is what i did:
javascript
var viewModel = (function () {
var self = this;
this.model = ko.observableArray([]);
$.getJSON('/myurl',
function (data) {
self.model = ko.mapping.fromJS(data, self.model);
ko.applyBindings(self.model);
});
});
ko.applyBindings(new viewModel());
html
<span data-bind="text: TestText"></span>
<select id="TestList"
data-bind="
options: TestList,
optionsText: 'Name',
optionsValue: 'ID',
optionsCaption: 'Please Select'"></select>

Related

Can I use jsTree preloaded with JSON data and also use Ajax

I have jsTree working with JSON data: the JSON data represents the server's file system, and the user can select a folder from the tree, which is then added to the folder input field). I don't want the page to load without the top three levels of the file system provided. However, I don't parse the whole file system because that would take too long.
Can I pre-populate jsTree with JSON data and use Ajax when the user opens nodes further down the tree which were not pre-populated, or do I have to use Ajax for the initial load as well?
Below I show my current code (without any Ajax), but only retrieving data down to one level for the sake of brevity: it returns C:\ and E:\ file systems from the server. This works, but I'm unclear how to introduce Ajax to this when the user tries to open a node further down the hierarchy.
<label for="folder">Selected Folder</label>
<input type="text" name="folder" id="folder">
<br>
<script type="text/javascript">
function createJSTree(jsondata)
{
$('#jstree').jstree(
{
"plugins" : ["themes","html_data","ui","cookie"],
'core':
{
'data': jsondata
}
}
)
.bind("select_node.jstree",
function (e, data)
{
var objNode = data.instance.get_node(data.selected);
document.getElementById('folder').value=objNode.id;
}
)
;
}
$(function() { var jsondata ={"text":"pclaptop","children":[{"id":"C:\\","text":"C:\\","children":[]},{"id":"E:\\","text":"E:\\","children":[]}]}; createJSTree(jsondata); })
</script>
Before I get into the ajax piece of the code I had to set the check_callback parameter in jsTree it enables editing of the jsTree. Next, I call `$('#jstree').jstree().create_node('#', parsedData, "last"); in the success method of jQuery's ajax call, and that did the trick. My solution is below:
index.html
<label for="folder">Selected Folder</label>
<input type="text" name="folder" id="folder">
<br>
<button id="create-node-button">
Create Node
</button>
<div id="jstree"></div>
<script type="text/javascript">
function createJSTree(jsondata) {
$('#jstree').jstree({
"plugins": ["themes", "html_data", "ui", "cookie"],
'core': {
'check_callback': true,
'data': jsondata
}
})
.bind("select_node.jstree",
function(e, data) {
var objNode = data.instance.get_node(data.selected);
document.getElementById('folder').value = objNode.id;
}
);
}
$(function() {
var jsondata = [{
"id": "pclaptop",
"parent": "#",
"text": "pclaptop"
},
{
"id": "C:\\",
"parent": "pclaptop",
"text": "C:\\"
},
{
"id": "E:\\",
"parent": "pclaptop",
"text": "E:\\"
},
{
"id": "F:\\",
"parent": "pclaptop",
"text": "F:\\"
}
];
createJSTree(jsondata);
$("#create-node-button").on("click", function() {
$.ajax({
url: "./data.json",
success: function(data){
var parsedData = JSON.parse(data);
$('#jstree').jstree().create_node('#', parsedData, "last");
}
});
});
});
</script>
data.json
{ "id" : "ajson5", "text" : "newly added" }
Lastly, here is a fiddle . I wasn't sure of how to properly set up the ajax call in jsfiddle so I did it locally instead.

How to have a custom value in JSON with JQueryi UI Autocomplete?

I'm using the JQuery UI autocomplete plugin (cached version) with JQuery-UI 1.11.1
Due to some server-side changes in the JSON I am using as source, I need to adapt my code.
Here is an example of my JSON:
[{
"name": "Varedo"
}, {
"name": "Varena"
}, {
"name": "Varenna"
}, {
"name": "Varese"
}]
produced by an URL with this style:
[url]/?name=vare
Since the GET variable is different from the default one ("term"), I already adapted my code for the custom request as suggested here:
$(function () {
var cache = {};
$("#searchTextField").autocomplete({
minLength: 3,
source: function (request, response) {
var term = request.term;
if (term in cache) {
response(cache[term]);
return;
}
$.getJSON("[url]", {
name: request.term
}, function (data, status, xhr) {
cache[term] = data;
response(data);
});
}
});
});
However I need to also adapt the code in order to use a custom JSON value (the default is "value" http://api.jqueryui.com/autocomplete/#option-source) which is in my case is "name" (as you can see from the JSON).
How can I do that?
At the moment this is what I get from the autocomplete:
So I guess I am somehow giving as response JS Objects and not strings.
Thanks in advance.
Currently you're saving the response as it is into your cache object, which is not valid format for jQuery UI autocomplete. You should convert the data into proper format digestable for autocomplete.
Either you should pass an array of strings, or an array of objects having label and value properties.
Since the response only contains name properties, you can convert it into an array of strings using jQuery map() method and save it in cache variable as follows:
$("#searchTextField").autocomplete({
minLength: 3,
source: function (request, response) {
var term = request.term;
if (term in cache) {
response(cache[term]);
return;
}
$.getJSON("[url]", {
name: request.term
}, function (data, status, xhr) {
cache[term] = $.map(data, function (obj) { // returns array of strings
return obj.name
});
// return the new array rather than original response
response(cache[term]);
});
}
});

AngularJS - How to data bind an object within a JSON object

Using the included http post, I should get back the JSON object below. I want to take the LeagueDictionary data in the JSON below and create an object so I can use it in a for each loop on my client, but I can't wrap my head around how to structure that code in the http call.
{
"Id": 0,
"UserName": null,
"NickName": null,
"Email": "email#company.com",
"Password": null,
"Admin": false,
"Validated": false,
"Key": "oOE0QbOhjK17pNeKDPEFti5On27R3b",
"LeagueDictionary": {
"1": "League #1",
"2": "League #2"
}
}
using this call:
$scope.getLeagues = function() {
$http({
method: 'POST',
url: 'http://xxxxxxxxxxxxxxxxxx',
data: ???,
})
}
If someone give me a nudge on how to data bind that particular part of the JSON, I'd appreciate the help. I'm not sure how to strip the LeagueDictionary section out and make an object out of it.
You could set up a service that gets your data, so you can get $http and such out of your controller. Say...
app.factory('DataService', function ($http) {
return {
get: function () {
return $http.get('data.json'); // post & url goes here
}
};
});
In your controller, use then to access the response data after promise has been resolved (catch() and finally() are available, too).
app.controller('MainCtrl', function ($scope, DataService) {
DataService.get().then(function (response) {
$scope.leagueDictionary = response.data.LeagueDictionary;
});
});
Related HTML template would be
<body ng-controller="MainCtrl">
<div ng-show="leagueDictionary">
<span ng-repeat="(key,val) in leagueDictionary">
{{ val }} <br>
</span>
</div>
</body>
Using your data this gives you
See example plunker here http://plnkr.co/edit/j6bmmQ
You can just access the LeagueDictionary property of the response, and then iterate over that in ng-repeat. Obviously I don't know exactly what your scopes look like, but this should get you started:
//JS
$http.post('/someUrl', { 'foo': 'bar' }).success(function(data) {
myController.myModel.leagueDictionary = data.LeagueDictionary;
});
//HTML
<tr ng-repeat="(leagueNum, leagueName) in leagueDictionary">

Backbone toJSON not rending

I am a complete n00b to Backbone.js, and have only been working with it for a few days. I am attempting to fetch JSON data to populate the model, and in this scenario I have two models that I need to generate. Here is the sample JSON I have been working with:
JSON
{
"status": "200",
"total": "2",
"items":
[{
"id": "1",
"name": "Here is another name",
"label": "Label for test",
"description": "A description for more information.",
"dataAdded": "123456789",
"lastModified": "987654321"
},
{
"id": "2",
"name": "Name of item",
"label": "Test Label",
"description": "This is just a long description.",
"dataAdded": "147258369",
"lastModified": "963852741"
}]
}
Backbone JS
// MODEL
var Service = Backbone.Model.extend({
defaults: {
id: '',
name: '',
label: '',
description: '',
dateAdded: '',
dateModified: ''
}
});
var service = new Service();
// COLLECTION
var ServiceList = Backbone.Collection.extend({
model: Service,
url: "./api/service.php",
parse: function(response) {
return response.items;
}
});
//
var serviceList = new ServiceList();
var jqXHR = serviceList.fetch({
success: function() {
console.log("Working!");
console.log(serviceList.length);
},
error: function() {
console.log("Failed to fetch!");
}
});
// VIEW for each Model
var ServiceView = Backbone.View.extend({
el: $('.widget-content'),
tagName: 'div',
template: _.template($('#service-template').html()),
initialize: function() {
this.collection.bind("reset", this.render, this);
},
render: function() {
console.log(this.collection);
this.$el.html('');
var self = this;
this.collection.each(function(model) {
self.$el.append(self.template(model.toJSON()));
});
return this;
}
});
//
var serviceView = new ServiceView({
collection: serviceList
});
console.log(serviceView.render().el);
html
<div class="widget-content">
<!-- Template -->
<script type="text/template" id="service-template">
<div><%= name %></div>
</script>
</div>
When I console log the serviceList.length I get the value 2, so I believe the JSON object is fetched successfully. I also get the "Working!" response for success too. However, in the view I am showing an empty object, which gives me an empty model.
I am still trying to understand the best way to do this too. Maybe I should be using collections for the "items" and then mapping over the collection for each model data? What am I doing wrong? Any advice or help is greatly appreciated.
I can see two problems. First, you want to remove serviceList.reset(list). Your collection should be populated automatically by the call to fetch. (In any case the return value of fetch is not the data result from the server, it is the "jqXHR" object).
var serviceList = new ServiceList();
var jqXHR = serviceList.fetch({
success: function(collection, response) {
console.log("Working!");
// this is the asynchronous callback, where "serviceList" should have data
console.log(serviceList.length);
console.log("Collection populated: " + JSON.stringify(collection.toJSON()));
},
error: function() {
console.log("Failed to fetch!");
}
});
// here, "serviceList" will not be populated yet
Second, you probably want to pass the serviceList instance into the view as its "collection". As it is, you're passing an empty model instance into the view.
var serviceView = new ServiceView({
collection: serviceList
});
And for the view, render using the collection:
var ServiceView = Backbone.View.extend({
// ...
initialize: function() {
// render when the collection is reset
this.collection.bind("reset", this.render, this);
},
render: function() {
console.log("Collection rendering: " + JSON.stringify(this.collection.toJSON()));
// start by clearing the view
this.$el.html('');
// loop through the collection and render each model
var self = this;
this.collection.each(function(model) {
self.$el.append(self.template(model.toJSON()));
});
return this;
}
});
Here's a Fiddle demo.
The call serviceList.fetch is made asynchronously, so when you try console.log(serviceList.length); the server has not yet send it's response that's why you get the the value 1, try this :
var list = serviceList.fetch({
success: function() {
console.log(serviceList.length);
console.log("Working!");
},
error: function() {
console.log("Failed to fetch!");
}
});

Angular JS detailed view with only one json

I'm introducing in Angular with its Tutorial "Phonecat".
Against the tutorial I'd like to build a simple app with a list and detail view with only one json, containing all informations.
The list-view (showing complete content of the json) works fine but I'm struggle with how to set my Angular services for the detail-view.
I am using the XHR method:
Controller.js:
function PlaygroundDetailCtrl($scope, Playground) {
$scope.playgrounds = Playground.query();
}
Services.js
angular.module('playgroundcatServices', ['ngResource']).
factory('Playground', function($resource){
return $resource('playgrounds/playgrounds.json', {}, {
query: {method:'GET', isArray:true}
});
});
playground.json
[
{
"id:" 1,
"properties": "true"
"lat": "51.347789"
"lon": "12.232234"
},
{
"id:" 2,
"properties": "false"
"lat": "51.347789"
"lon": "12.766667"
}
]
I want Angular to display only one entry (id:1) with its properties.
What is the smartest way to do that: showing again all and then filter?
I am stumped.
Use an Angular filter on your view (there's no need to filter the data on the service):
<div ng-repeat="entry in playgrounds | filter:{id: 1}">
<p>properties: {{entry.properties}}</p>
<p>lat: {{entry.lat}}</p>
<p>lon: {{entry.lon}}</p>
</div>
jsfiddle: http://jsfiddle.net/bmleite/Ad6u9/
This worked out quite good:
Controller:
function PlaygroundDetailCtrl($scope, $routeParams, $http) {
$http.get('playgrounds/playgrounds.json').success(function(data){
angular.forEach(data, function(item) {
if (item.id == $routeParams.playgroundId)
$scope.playground = item;
});
});
I've got exactly the same scenario now I think (I'm guessing you're developing with 'mobile' in mind (as I am and want to minimise data transfer) - I'm using one 'master json file', and then on my detail view just filtering that json file (so that the json doesn't have to be reloaded) on the ID value.
This is totally untested but your code from the original question should be modified something like this:
angular.module('playgroundcatServices', ['ngResource'])
.factory('Playground', function($resource){
return $resource('playgrounds/playgrounds.json', {}, {
query: {method:'GET', isArray:true}
});
});
function PlaygroundDetailCtrl($scope, Playground) {
Playground.query(
// params (none in this case)
{},
// Success
function (data) {
$scope.playgrounds = data.filter(function (o) {
return o.id == $routeParams.playgroundId; // assuming you've set this up in your routes definition
})[0];
},
// Error
function (data) {
//error handling goes here
}
);
}
You may want to put something like $scope.isDataLoaded = true; in your 'success' handler as well, and do a watch on that, to use to check to see when the data has finished loading (eg. for in a directive).
I'm not super happy with the [0] in there, but it think the solution is better than a forEach loop myself.