Knockout nested model properties - json

I need a feature like a repeating table in my web form and need to store my data in a JSON format like this:
[
{
"id": 1, "name": "T01", "title": "T01 form title", "totalPoints": "total of all points for sections below",
"sections":
[
{ "section": "First section", "point": 4 },
{ "section": "Second section", "point": 5 }
]
},
{
"id": 2, "name": "T02", "title": "T02 form title", "totalPoints": "total of all points for sections below",
"sections":
[
{ "section": "First section", "point": 4 },
{ "section": "Second section", "point": 5 }
]
}
]
I'm using knockout and I implemented top level of the structure below, but struggling with a nested sections.
Here is my attempts to structure my Model, please advise what option to use, or if this incorrect, please advise the right option:
function Form(data)
{
this.Id = ko.observable(data.Id);
this.Name = ko.observable(data.Name);
this.Title = ko.observable(data.Title);
this.Total = ko.observable(data.Total);
// Option 1, like an array
this.Sections = ko.observableArray([
{
Section: data.Section,
Point: data.Total
}
]);
// Option 2, like a function
function Sections(data) {
this.Section = ko.observable(data.Section),
this.Point = ko.observable(data.Point)
}
}
Later I push this data as a model to observable array like this, again I can push the top level, but couldn't nested properties:
self.addForm = function () {
self.forms.push(
new Form({
Id: this.id(),
Name: this.name(),
Title: this.title(),
Total: function() // TODO
// Sections nested properties implementation
})
);
self.name("");
};

I'd say it's best to define two viewmodels:
Form, and
Section
Your Form will have three kinds of properties:
Regular ko.observable values, for Id, Name and Title
Note: If some of those are static, it's best to not make them observable. I can imagine the Id will never change: you can signal this to other readers of your code by making it a regular string.
An observableArray for your list of Sections
A pureComputed for Total that sums up all your Section points
function Section(points, title) {
// TODO: check if you need these to be observable
this.points = points;
this.title = title;
};
// Create a new Section from a plain object
Section.fromData = function(data) {
return new Section(data.point, data.section);
};
function Form(id, name, title, sections) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.title = ko.observable(title);
// Map using our static constructor helper
this.sections = ko.observableArray(
sections.map(Section.fromData)
);
this.total = ko.pureComputed(function() {
return this.sections().reduce(function(sum, section) {
return sum + section.points;
}, 0);
}, this);
}
// A helper to get from your data object to a new VM
Form.fromData = function(data) {
return new Form(data.id, data.name, data.title, data.sections);
}
// Apply bindings
ko.applyBindings({
forms: getTestData().map(Form.fromData)
});
// Your test data
function getTestData() {
return [{
"id": 1,
"name": "T01",
"title": "T01 form title",
"totalPoints": "total of all points for sections below",
"sections": [{
"section": "First section",
"point": 4
},
{
"section": "Second section",
"point": 5
}
]
},
{
"id": 2,
"name": "T02",
"title": "T02 form title",
"totalPoints": "total of all points for sections below",
"sections": [{
"section": "First section",
"point": 4
},
{
"section": "Second section",
"point": 5
}
]
}
]
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: forms">
<li>
<p data-bind="text: title"></p>
<ul data-bind="foreach: sections">
<li>
<span data-bind="text: title"></span> (
<span data-bind="text: points"></span>)
</li>
</ul>
<span data-bind="text: 'total: (' + total()"></span>)
</li>
</ul>
The Section class is a bit plain; if you don't have additional requirements you could choose to use plain objects instead.

Related

Angular dynamic forms created with JSON data

Dear reader. I am trying to make dynamic forms with Json data that has been red. The dynamic form is based on the example of Angular as seen here: https://angular.io/guide/dynamic-form
The edits I made are that I read data from an external Json file and try to load those instead of the hardcoded one in the file 'question.service.ts' as seen in the link.
This is how my Json file looks like:
{
"formInfo": {
"name": "test"
},
"fields": [
{
"controlType": "textbox",
"key": "firstName",
"label": "Voornaam",
"required": true,
"value": "Mark",
"order": 1
},
{
"controlType": "textbox",
"key": "surName",
"label": "Achternaam",
"required": true,
"order": 2
},
{
"controlType": "textbox",
"key": "emailAddress",
"label": "Email",
"required": false,
"order": 3
},
{
"controlType": "dropdown",
"key": "brave",
"label": "Beoordeling",
"required": "",
"order": 4,
"options": {
"solid": "Solid",
"great": "Great",
"good": "Good",
"unproven": "Unproven"
}
}
]
}
And my function to retrieve the data and return as observable (in question.service.ts) looks like:
getQuestions2() : Observable<QuestionBase<any>[]> {
let questions: QuestionBase<any>[] = [];
const exampleObservable = new Observable<QuestionBase<any>[]>((observer) =>
{
let url = "../assets/exampleData.json"
this.http.get(url).subscribe((data) => {
for (let x of data['fields']){
if (x.controlType == "textbox"){
let textboxItem = new TextboxQuestion({
key: x.key,
label: x.label,
value: x.value,
order: x.order
})
questions.push(textboxItem);
}
else if (x.controlType == "dropdown"){
let dropDownItem = new DropdownQuestion({
key: x.key,
label: x.label,
value: x.value,
options: x.options,
order: x.order
})
questions.push(dropDownItem);
}
}
})
observer.next(questions.sort((a, b) => a.order - b.order));
})
return exampleObservable;
}
and the code that connects the service with the frontend looks like this:
export class AppComponent implements OnInit {
questions: any[];
constructor(private service: QuestionService) {
this.getaSyncData();
//this.questions = this.service.getQuestions();
//console.log(this.questions);
}
getaSyncData(){
this.service.getQuestions2()
.subscribe((data) => this.questions = data);
console.log(this.questions);
}
I solved this finally for those who will have similar issues in the future
I was not able to load forms into the html even though I was correctly reading the data out of the JSON file and printing it in the console. I added a *ngIf in the div where you load in your data. In the example of Angular.io its in the template on App.component.html. Yes, it was this simple.

MongoDB, NodeJS: updating an embedded document with new members

Using: MongoDB and native nodeJS mongoDB driver.
I'm trying to parse all the data from fb graph api, send it to my API and then save it to my DB.
PUT handling in my server:
//Update user's data
app.put('/api/users/:fbuser_id/:category', function(req, res) {
var body = JSON.stringify(req.body);
var rep = /"data":/;
body = body.replace(rep, '"' + req.params.category + '"' + ':');
req.body = JSON.parse(body);
db.fbusers.update({
id: req.params.fbuser_id
}, {
$set: req.body
}, {
safe: true,
multi: false
},
function(e, result) {
if (e) return next(e)
res.send((result === 1) ? {
msg: 'success'
} : {
msg: 'error'
})
});
});
I'm sending 25 elements at a time, and this code just overrides instead of updating the document.
Data I'm sending to the API:
{
"data": [
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
...and so on
}
]
}
Basically my API changes "data" key from sent json to the category name, f.e.:
PUT to /api/users/000/likes will change the "data" key to "likes":
{
"likes": [
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
...and so on
}
]
}
Then this JSON is put to the db.
Hierarchy in mongodb:
{
"_id": ObjectID("556584c8e908f0042836edce"),
"id": "0000000000000",
"email": "XXXX#gmail.com",
"first_name": "XXXXXXXX",
"gender": "male",
"last_name": "XXXXXXXXXX",
"link": "https://www.facebook.com/app_scoped_user_id/0000000000000/",
"locale": "en_US",
"name": "XXXXXXXXXX XXXXXXXXXX",
"timezone": 3,
"updated_time": "2015-05-26T18:11:59+0000",
"verified": true,
"likes": [
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
"category": "App page",
"name": "SoundCloud",
"id": "7919071058",
"created_time": "2013-09-16T18:16:59+0000"
},
{
....and so on
}
]
}
So the problem is that my api overrides the field (in this case "likes") with newly sent data, instead of appending it to already existing data document.
I am pretty sure that I should be using other parameter than "$put" in the update, however, I have no idea which one and how to pass parameters to it programatically.
Use $push with the $each modifier to append multiple values to the array field.
var newLikes = [
{/* new item here */},
{/* new item here */},
{/* new item here */},
];
db.fbusers.update(
{ _id: req.params.fbuser_id },
{ $push: { likes: { $each: newLikes } } }
);
See also the $addToSet operator, it adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array.

How to display data from a json file in angularjs?

I have a json something like this
{
"count": 67,
"0": {
"id": "2443",
"name": "Art Gallery",
"category": {
"id": "2246",
"name": "Gifts & Memories"
},
"deckLocation": [
{
"id": "2443",
"deck": {
"deckNo": "7",
"deckName": "Promenade "
},
}
]
},
"1": {
"id": "7198",
"name": "Captain's Circle Desk",
"category": {
"id": "352",
"name": "Other Services"
},
"deckLocation": [
{
"id": "7198",
"deck": {
"deckNo": "7",
"deckName": "Promenade "
},
},
{
"id": "7198",
"deck": {
"deckNo": "7",
"deckName": "Promenade "
},
}
]
}
}
I want to display all names which is inside the "0", "1" array. I can able to list a specific name but not all. The fist name will display which I written in the following code. But I need to display all 0, 1, 2, 3 etc names dynamically.
data[0].name
Please help me.
Thank you.
use ng-repeat function to do so.
<div ng-repeat = "names in data">
<P>{{names.name}}</P>
</div>
Let us say you have a service like this:-
var todoApp = angular.module("todoApp",[]);
todoApp.factory('dbService', ['$q','$http',function ($q , $http) {
var service ={};
service.getUrl = function (urlToGet) {
var svc=this;
var deferred = $q.defer();
var responsePromise = $http.get(urlToGet);
responsePromise.success(function (data, status, headers, config) {
deferred.resolve(data); });
responsePromise.error(function (data, status, headers, config) {
deferred.reject({ error: "Ajax Failed", errorInfo: data });});
return (deferred.promise);
}
return service;
}]);
And you want to load a file named '/js/udata.json'. Here is how in you controller you can load this file:-
todoApp.controller("ToDoCtrl", ['$scope','$timeout','dbService',function($scope, $timeout, dbService)
{
$scope.todo={};
$timeout(function(){
dbService.getUrl('/js/udata.json').then(function(resp){
$scope.todo=resp;
});},1);
};
Hope this helps!
You have data[0].name right?
then why don't you loop through that to get all the name elements in those arrays.
like...
for(i=1;i<data.length;i++)
{
console.log(data[i].name);
}

How do I use knockout to bind nested foreach loops on dynamically added properties?

I have a collection of items. The items are broken down into "types" and then further divided within the type into "categories". I do not know the names of the "types" or the "categories" before hand.
I would like to do some nested foreach binding to represent the data hierarchically. Something like this:
<ul data-bind="foreach: OrderItems.Types">
<li>
ItemType: <span data-bind='text: $data'></span>
<ul data-bind="foreach: Categories">
<li>
Category: <span data-bind='text: $data'></span>
<ul data-bind="foreach: OrderItems">
<li>
Item: <span data-bind="text: Name"> </span>
</li>
</ul>
</li>
</ul>
</li>
var order = {
"OrderNumber": "394857",
"OrderItems": {
"Types": {
"Services": {
"Categories": {
"carpet cleaning": {
"OrderItems": [
{
"OrderItemID": "9d398f88-892c-11e3-8f31-18037335d26a",
"Name": "ARug-Oriental Rugs (estimate on site)"
},
{
"OrderItemID": "9d398f53-892c-11e3-8f31-18037335d26a",
"Name": "C1-Basic Cleaning (per room)"
},
{
"OrderItemID": "9d398f54-892c-11e3-8f31-18037335d26a",
"Name": "C2-Clean & Protect (per room)"
},
{
"OrderItemID": "9d398f55-892c-11e3-8f31-18037335d26a",
"Name": "C3-Healthy Home Package (per room)"
}
]
},
"specialty": {
"OrderItems": [
{
"OrderItemID": "9d398f8f-892c-11e3-8f31-18037335d26a",
"Name": "SOTHR-Other"
}
]
},
"tile & stone": {
"OrderItems": [
{
"OrderItemID": "9d398f8e-892c-11e3-8f31-18037335d26a",
"Name": "TILE-Tile & Stone Care"
}
]
},
"upholstery": {
"OrderItems": [
{
"OrderItemID": "9d398f7b-892c-11e3-8f31-18037335d26a",
"Name": "U3S1-Upholstery - Sofa (Seats 3: 7 linear feet)"
},
{
"OrderItemID": "9d398f7c-892c-11e3-8f31-18037335d26a",
"Name": "U3S2-Upholstery - Sofa - Clean & Protect (Seats 3: 7 linear feet"
}
]
}
}
},
"Products": {
"Categories": {
"carpet cleaning": {
"OrderItems": [
{
"OrderItemID": "9d398f84-892c-11e3-8f31-18037335d26a",
"Name": "PLB-Leave Behind Item"
}
]
}
}
}
}
}
};
var viewModel = ko.mapping.fromJS(order);
ko.applyBindings(viewModel);
here's a fiddle with the above code: http://jsfiddle.net/mattlokk/6Q5f7/5/
To bind against your structure, you would need to turn the objects into arrays. Given that you are using the mapping plugin, the easiest way would likely be to use a binding that translates an object with properties to an array of key/values.
Here is a sample binding:
ko.bindingHandlers.objectForEach = {
init: function(element, valueAccessor, allBindings, data, context) {
var mapped = ko.computed({
read: function() {
var object = ko.unwrap(valueAccessor()),
result = [];
ko.utils.objectForEach(object, function(key, value) {
var item = {
key: key,
value: value
};
result.push(item);
});
return result;
},
disposeWhenNodeIsRemoved: element
});
//apply the foreach bindings with the mapped values
ko.applyBindingsToNode(element, { foreach: mapped }, context);
return { controlsDescendantBindings: true };
}
};
This will create a computed on-the-fly that maps the object to an array of key/values. Now you can use objectForEach instead of foreach against your objects.
Here is a basic sample: http://jsfiddle.net/rniemeyer/nn3jg/ and here is an example with your fiddle: http://jsfiddle.net/rniemeyer/47Wbe/

Loading TreeStore with JSON that has different children fields

I am having a JSON data like below.
{
"divisions": [{
"name": "division1",
"id": "div1",
"subdivisions": [{
"name": "Sub1Div1",
"id": "div1sub1",
"schemes": [{
"name": "Scheme1",
"id": "scheme1"
}, {
"name": "Scheme2",
"id": "scheme2"
}]
}, {
"name": "Sub2Div1",
"id": "div1sub2",
"schemes": [{
"name": "Scheme3",
"id": "scheme3"
}]
}
]
}]
}
I want to read this into a TreeStore, but cannot change the subfields ( divisions, subdivisions, schemes ) to be the same (eg, children).
How can achieve I this?
When nested JSON is loaded into a TreeStore, essentially the children nodes are loaded through a recursive calls between TreeStore.fillNode() method and NodeInterface.appendChild().
The actual retrieval of each node's children field is done within TreeStore.onNodeAdded() on this line:
dataRoot = reader.getRoot(data);
The getRoot() of the reader is dynamically created in the reader's buildExtractors() method, which is what you'll need to override in order to deal with varying children fields within nested JSON. Here is how it's done:
Ext.define('MyVariJsonReader', {
extend: 'Ext.data.reader.Json',
alias : 'reader.varijson',
buildExtractors : function()
{
var me = this;
me.callParent(arguments);
me.getRoot = function ( aObj ) {
// Special cases
switch( aObj.name )
{
case 'Bill': return aObj[ 'children' ];
case 'Norman': return aObj[ 'sons' ];
}
// Default root is `people`
return aObj[ 'people' ];
};
}
});
This will be able to interpret such JSON:
{
"people":[
{
"name":"Bill",
"expanded":true,
"children":[
{
"name":"Kate",
"leaf":true
},
{
"name":"John",
"leaf":true
}
]
},
{
"name":"Norman",
"expanded":true,
"sons":[
{
"name":"Mike",
"leaf":true
},
{
"name":"Harry",
"leaf":true
}
]
}
]
}
See this JsFiddle for fully working code.