Knockout Mapping Plugin and Data-Bind from Simple JSON - json

I'm having some problems figuring out why this simple Knockout mapping isn't working. I'm not sure if the returned JSON is invalid or if my mappings are wrong or if it's simply the bindings.
The data structure is a parent Conversation object with an array of Message objects.
On the bindings I'm using foreach: Conversation at the moment because I have this working if I wrap the whole thing a single element array.
My bindings
<div data-bind="foreach: conversation">
<div data-bind="foreach: messages">
<div class="well">
<div data-bind="text: sender_name"></div>
<div data-bind="text: subject"></div>
<div data-bind="text: body"></div>
<div data-bind="text: updated_at"></div>
</div>
</div>
</div>
My ViewModel, faked JSON and mappings
// Sample JSON to return for initialization; 2 second delay
var conversationData = {
json: $.toJSON(
{"id":8,"subject":"Hello JB! Email two!",
"updated_at":"Sep 27",
"originator":"James Pablo",
"count_messages":"(2)",
"messages":[{"subject":"RE: Hello JB! Email two!",
"body":"Thanks for the message!",
"sender_name":"Joe Flynn","updated_at":"Sep 27"},
{"subject":"Hello JB! Email two!",
"body":"Body text",
"sender_name":"James Pablo",
"updated_at":"Sep 27"}
]
}
),
delay: 1
}
function Conversation(data) {
ko.mapping.fromJS(data, {}, this);
}
function Message(data) {
ko.mapping.fromJS(data, {}, this);
}
var map = {
create: function(options) {
return new Conversation(options.data);
},
messages: function(options) {
return new Message(options.data);
}
}
var ViewModel = function() {
var self = this;
self.conversation = ko.observable();
// Use JSFiddle echo to simulate an AJAX service
(function() {
$.ajax({ url:"/echo/json/", data:conversationData, type:"POST",
success:function(data)
{
// Map the returned JSON to the View Model
ko.mapping.fromJS(data, map, self.conversation);
}
});
})();
console.log(self.conversation());
};
ko.applyBindings(new ViewModel());
This JSFiddle works when I'm returning the JSON data wrapped in a single element array.
If I remove the array wrapper from the JSON in this JSFiddle (which is what I want as I only want to render a single conversation) then I can't get it to work. Any ideas?

Create function only works on arrays.
Try this
http://jsfiddle.net/C46pU/2/
Removed Convension from map literal and did
self.conversation(new Conversation(data));

Related

angularjs get data from Json using $resourse

Good afternoon! Learning Angularjs. Below is the structure of the project.
I have a service that reads data from json
var WebKrServices = angular.module('WebKrServices', ['ngResource']);
WebKrServices.factory('DataPlant', ['$resource',
function($resource){
return $resource('plants/:plantId.json', {}, {
query: {method:'GET', params:{plantId:'plants'}, isArray:true}
});
}]);
And Controller
var WebKrControllers = angular.module('WebKrControllers', []);
WebKrControllers.controller('PlantsCtrl', ['$scope', 'DataPlant',
function($scope, DataPlant) {
$scope.plants = DataPlant.query();
}]);
which transmits this information to the html
<div ng-repeat="plant in plants">
<h2 class="text-center">{{plant.name}}</h2>
</div>
And, in fact question. In html I see data from json, and the controller when accessing the plants I see an empty object?
for (var p in plants) {
. . .
}
How to get data from the plants in the controller?
Thank you all for your answers.
Cause it is asynchronous call. After $scope.plants = DataPlant.query();, plants remain undefined until data arrives (Well, it is not exactly undefined, you can inspect it in debugger). When data arrives - $scope.plants get resolved and html is updated. To run some code after data arrives, use callbacks:
$scope.plants = DataPlant.query(function(response) {
console.log($scope.plants);
}, function (response) {
console.log('Error');
});

AngularJS: Dynamically change expression after $watch

The main thing I want to do here is:
When the form is submitted, a http get request will be done (VerbController)
On success, a JSON string is returned
I copy the JSON string into a factory object (called VerbFactory)
I want to output the content of the JSON string in a div through another controller (OutputController), I took the attribute "name" as an example here.
To achieve this (point 4), I watched for a change in the VerbFactory object and when the JSON string after requesting gets loaded into the object, I want to store it in a variable of the OutputController, so that I can make an expression for it in my HTML.
But it does not work right now. It seems that this.verb is in another scope than the controller scope. I have difficulties understand the difference between $scope and this here, even though I have read a decent amount of articles about the difference between those two.
How do I solve this problem? Do I miss something obvious?
NB: I added some jQuery that puts the attribute "name" of the JSON into a debug div, and it works as expected. But the AngularJS expression {[{outputCtrl.verb["#attributes"]["name"]}]} does not work.
HTML:
<div class="container">
<div class="row">
<div id="debug" class="col-xs-12 col-sm-12">
</div>
<div class="col-xs-12 col-sm-12" ng-controller="OutputController as outputCtrl">
{[{outputCtrl.test}]}
{[{outputCtrl.verb["#attributes"]["name"]}]}
</div>
</div>
</div>
JS:
(function() {
var app = angular.module('LG', []).config(function($interpolateProvider){
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
});
app.factory("VerbFactory", function(){
var json = {};
var available = false;
return {
getJSON: function() {
return json;
},
setJSON: function(newObj) {
angular.copy(newObj, json);
available = true;
},
isAvail: function() {
return available;
},
resetAvail: function() {
available = false;
}
};
});
app.controller("VerbController", ['$http', 'VerbFactory', function($http, VerbFactory){
this.verb = "";
this.requestVerb = function() {
VerbFactory.resetAvail();
var that = this;
$http.get('/request/' + that.verb).
success(function(data) {
VerbFactory.setJSON(data);
}).
error(function() {
});
this.verb = "";
};
}]);
app.controller("OutputController", ['$scope', 'VerbFactory', function($scope, VerbFactory){
this.test = "Test!";
$scope.$watch(VerbFactory.isAvail, function(){
this.verb = VerbFactory.getJSON();
$('#debug').append('<p>'+ this.verb["#attributes"]["name"] +'</p>');
});
}]);
})();
this inside of $scope.$watch callback refers to the callback scope, not the outer scope of OutputController. Use var self = this to refer to the OutputController.
ControllerAs Syntax
OutputController.js
var self = this
$scope.$watch(VerbFactory.isAvail, function(){
self.verb = VerbFactory.getJSON();
//etc
});
Regular Controller Syntax
OutputController.js
$scope.$watch(VerbFactory.isAvail, function() {
$scope.verb = VerbFactory.getJSON();
//etc
});

Converting Angular's $scope to (Controller as) while using $http

I'm using Angular 1.3 I have changed my code to use Controller as from using $scope.
I have a url:
http://localhost:3640/api/v1/topics
That gets the following json:
[
{
topicId: 17,
title: "This is another BRAND SPANKIN new topic",
body: "This is a message in the body of another topic ftw!",
created: "2014-11-27T05:37:49.993",
replies = null
},
{
topicId: 18,
title: "This is another BRAND new topic",
body: "This is a message in the body of a topic wow!",
created: "2014-11-27T05:37:49.993",
replies = null
}
]
I also have a page called index-home.js
var app = angular.module("myApp", []);
app.controller("homeIndexController", ["$http", function ($http) {
var msg = this;
msg.dataCount = 0;
msg.replies = [];
$http.get("/api/v1/topics?includeReplies=true")
.success(function(data) {
//Success
msg.replies = data;
})
.error(function() {
//Error
alert('error/failed');
});
}]);
On my page I use the following bindings:
<div id="ngController" ng-controller="homeIndexController as hic">
...
<h3>Message count: {{hic.dataCount}}</h3>
...
<div class="message row" data-ng-repeat="i in hic.data">
<div class="title">{{i.title}}</div>
<div class="date">{{i.created}}</div>
<div class="contents">{{i.body}}</div>
</div>
I know that the url in the $http is working because I tried it by itself in the browser and fiddler and it returned the json. But when I use it in my angular app I get the .error result (which is a alert saying FAIL.
I have tried removing the http://localhost:3640 and just using /api/v1/topics when I do that, I don't get the error result anymore. I know my controller is working and binding to the page because I get back 0 for dataCount.
What am I doing wrong in the $http method? Is my syntax wrong?
The error is in your ng-repeat attribute. You do not have a data variable in your scope.
Change hic.data to hic.replies and it will work.
<div id="ngController" ng-controller="homeIndexController as hic">
...
<h3>Message count: {{hic.dataCount}}</h3>
...
<div class="message row" data-ng-repeat="i in hic.replies">
<div class="title">{{i.title}}</div>
<div class="date">{{i.created}}</div>
<div class="contents">{{i.body}}</div>
</div>
Alright dude, this is a real rookie mistake, look at your controller:
app.controller("homeIndexController", ["$http", function ($http) {
var msg = this;
msg.dataCount = 2;
msg.replies = [];
$http.get("/api/v1/topics")
.success(function(data) {
//Success
msg.replies = data;
})
.error(function() {
//Error
alert('eror/failed');
});
}])
You are setting this to msg, then setting msg.replies to equal data if success.
This is ok.
Your problem then comes in your ng-repeat when you say:
<div data-ng-repeat="i in hic.data">
hic.data doesn't exist. data was the item returned from the .success(function()) of the $http.get. You then take that data and assign it to msg.replies. So your ng-repeat="" should look like this:
<div data-ng-repeat="i in hic.replies">
Understand? This is basic stuff... I, uh I mean you should feel really silly.

angular sortable with model data from json

I wanna use angular sortable on my app. But my model is dynamically populated from several json files with $http.get() function. All that ngSortable see from the model is just an empty array. And it won't get the new data from the JSON file. Is there any workaround for this?
$scope.jsons = ["data1.json", "data2.json"];
$scope.abc = [];
angular.forEach($scope.jsons, function(value, key){
$http.get(value).success (function(data){
$scope.abc.push(data);
});
});
$scope.sortableOptions = {
accept: function (sourceItemHandleScope, destSortableScope) {return true}
};
<div ng-model="abc" as-sortable="sortableOptions">
<div ng-repeat="x in abc" as-sortable-item>
<div as-sortable-item-handle>{{x.name}}</div>
</div>
</div>
I had the same problem with ng-sortable: everything worked fine with static data but not with JSON data that come asynchronous from $http.get().
There are two solutions:
Leave the controller as it and, in the html part, replace both occurrences of "abc" with "$parent.abc"
Instead of directly access the 'abc' array, use an intermediate object, like this:
$scope.tmpObject = {};
$scope.tmpObject.abc=[];
...
$http.get(value).success (function(data){
$scope.tmpObject.abc.push(data);
});
...
<div ng-model="tmpObject.abc" as-sortable="sortableOptions">
<div ng-repeat="x in tmpObject.abc" as-sortable-item>
<div as-sortable-item-handle>{{x.name}}</div>
</div>
</div>
use service $q
like
//first make a factory using $q
yourappname.factory("factoryname",function($http, $q){
var _getDetails=function(value){
var deferred = $q.defer();
$http.get(value).then(function(modal){
deferred.resolve(modal);
});
return deferred.promise;
};
return {
_getDetails:_getDetails
};
});
// then in your controller use this factory
$scope.jsons = ["data1.json", "data2.json"];
$scope.abc = [];
/**angular.forEach($scope.jsons, function(value, key){
$http.get(value).success (function(data){
$scope.abc.push(data);
});
});
***/
angular.forEach($scope.jsons, function(value, key){
var promiseData= factoryname._getDetails(value);
promiseData.then(function(result){
if(result.data)
{
$scope.abc.push(result.data);
}
});
});
$scope.sortableOptions = {
accept: function (sourceItemHandleScope, destSortableScope) {return true}
};
<div ng-model="abc" as-sortable="sortableOptions">
<div ng-repeat="x in abc" as-sortable-item>
<div as-sortable-item-handle>{{x.name}}</div>
</div>
</div>

Completely wipe out the dijit (dojo) tree from memory and free its placeholder

I begin with piece by piece of code so that the problem description becomes clear.
I have a piece of HTML code as:
<div id="center" class="column" dojoType="dijit.layout.TabContainer">
<div id="first" dojoType="dijit.layout.ContentPane" title="first" selected="true">
<div id="first_content"></div>
</div>
<div id="second" dojoType="dijit.layout.ContentPane" title="second">
<div id="second_content"></div>
</div>
</div>
I have a javascript function to load the dijit trees into HTML :
function load()
{
//load data
dojo.xhrGet(firsthierarchy("first_content", "file1.json"));
dojo.xhrGet(secondhierarchy("second_content", "file2.json"));
}
function firsthierarchy(node, url){
return {
url: url,
node: dojo.byId(node),
handleAs: "json",
load: loadfirsthierarchy
};
}
function secondhierarchy(node, url){
return {
url: url,
node: dojo.byId(node),
handleAs: "json",
load: loadsecondhierarchy
};
}
function loadfirsthierarchy(data, xhr)
{
if(xhr.args.node)
{
store1 = new dojo.data.ItemFileWriteStore({data: data});
treeModel1 = new dijit.tree.ForestStoreModel({
store: store1,
query: {
"type": "continent"
},
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
tree1 = new dijit.Tree({
model: treeModel1,
},xhr.args.node.id);
}
}
function loadsecondhierarchy(data, xhr)
{
if(xhr.args.node)
{
store2 = new dojo.data.ItemFileWriteStore({data: data});
treeModel2 = new dijit.tree.ForestStoreModel({
store: store2,
query: {
"type": "continent"
},
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
tree2 = new dijit.Tree({
model: treeModel2,
},xhr.args.node.id);
}
}
All of the above functions are working fine. Now, I want to have a reset function such that it can wipe out the existing trees in the "first_content" and "second_content" div and load those divs with new trees having new content. Eg:
function reset()
{
// here I want to completely wipe out the exiting trees in all of the dojo contentpanes.
// And I want to load the contentpanes with entire new set of data.
// Maybe like :
// dojo.xhrGet(firsthierarchy("first_content", "file3.json"));
// dojo.xhrGet(secondhierarchy("first_content", "file4.json"));
}
I have no idea on how to implement the reset function. Could you please provide me a clue.
Keep references to the tree, treeModel and store, then define your function as:
reset: function() {
myTree.destroyRecursive();
myTreeModel.destroyRecursive(); // I'm not 100% sure you need this, destroying the tree may do this automatically
delete myStore
}
That should do the trick
The way I'd do this is to create a module using dojo.provide called TreeWrapper() which handles the Tree lifecycle (fetching the data, creating the tree, killing the tree). This wrapper module then has the reset function above and can contain references to all the different variables.
You'll also need to keep track of any dojo.subscribe's you have for the tree and unsubscribe from those.