How do I customize knockout mapping creation in nested model? - json

Completely new to Knockout and I am trying to map a JSON response from the server to specific models using the knockout mapping plugin. The models are nested and I'm trying to override object construction using the create callback even in the nested models. However, it doesn't appear that my mapping options are being read properly. Example JSON:
{
"EmployeeFeedbackRequestSubmissions": [
{
"EmployeeFeedbackRequestSubmissionId": 0,
"Employee": "John Smith0",
"EmployeesWorkedWith": [
{
"EmployeeName": "Joe Smith",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Michael Jones",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Jason Smith",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Robert Will",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
}
]
}
// more EmployeeFeedbackRequestSubmissions
]
}
Mapping options:
var mappingOptions = {
// overriding default creation/initialization code
'EmployeeFeedbackRequestSubmissions': {
create: function (options) {
return (new(function () {
this.EmployeeHeading = ko.computed(function () {
return "Performance Evaluation Employee: " + this.Employee();
}, this);
ko.mapping.fromJS(options.data, {}, this);
})());
},
'EmployeesWorkedWith': {
create: function (options) {
return new instance.EmployeesWorkedWithModel(options.data);
}
}
}
};
Sample fiddle with full example: http://jsfiddle.net/jeades/9ejJq/2/
The result should be the ability to use the computed nameUpper from the EmployeesWorkedWithModel. I'm also open to suggestions about a better way to do this as this may not be the best way to handle this.

You were almost there. Straight to it working: http://jsfiddle.net/jiggle/na93A/
The mappings options object doesn't need to be nested, the mapping plug in will look up the mapping from the name, when you pass them to ko.mapping.fromJSON
So your mapping options object should be single level:
var self = this;
self.mappingOptions = {
// overriding default creation/initialization code
'EmployeeFeedbackRequestSubmissions': {
create: function (options) {
return (new(function () {
this.EmployeeHeading = ko.computed(function () {
return "Performance Evaluation Employee: " + this.Employee();
}, this);
ko.mapping.fromJS(options.data, self.mappingOptions, this);
})());
}
},
'EmployeesWorkedWith': {
create: function (options) {
// return new instance.EmployeesWorkedWithModel(options);
return (new(function(){
ko.mapping.fromJS(options.data, {}, this);
this.nameUpper = ko.computed(function () {
return this.EmployeeName().toUpperCase();
}, this);
})());
}
}
};
Notice I have used "self" as your local reference to 'this' instead of 'instance', just to make the code easier to read (as you used 'instance' in the main viewmodel).
I have also made the mappingOptions object part of the FeedbackViewModel, as we need to pass this into the mapping.fromJS call so when it sees the 'EmployeesWorkedWith' level in the data it will have the mappingOptions for it.
From:
ko.mapping.fromJS(options.data, {}, this);
To:
ko.mapping.fromJS(options.data, self.mappingOptions, this);
You can then move your creation code for 'EmployeesWorkedWith' level into the create (you could call a function, but I've kept it together in the mappingOptions as shown above, like the way you were creating the 'EmployeeFeedbackRequestSubmissions' level.
You can then get rid of the instance.EmployeesWorkedWithModel function altogether.
A working fiddle can be found here:
http://jsfiddle.net/jiggle/na93A/
Alternatively, you could create separate mappingOptions object when you are in the create for 'EmployeeFeedbackRequestSubmissions' and not have the mappings for all levels in one object, which can be seen in this fiddle http://jsfiddle.net/jiggle/Avam7/
Depends on your coding style which way you prefer, and would be important to separate them out if you had different mapping needs for different levels and they had the same collection name.
eg.
Employees
Employee
Employees (you might need different computeds, etc. at this level)
If so, you would use the second option (separate the mappingOptions and pass to the level that will use it)
I've added some console.log statements to the fiddles so you can see values as the code runs in the console, which will help to understand how it's working.
Hope it helps.

Nice thing with ko.mapping is how automated the process can be.
Check out the results in http://jsfiddle.net/9ejJq/26/
You'll note how we only use one declared mapping to kick things off.
feedbackMappingOptions = {
create: function (options) {
return new FeedbackViewModel(options.data);
}
};
From there on, each view model triggers a mapping for their child objects. You could go as far as creating a mapping option for each or, as you see for the final ProjectsWorked object under the EmployeesWorkedWith, we just throw the data right at a mapping and ko.mapping does the rest. Hope this helped.

Related

DataTables - -how to get rowgrouping working with objects as data

I am using datatables and here is how my data looks like:
{
"data": [{
"request": {
"responsible": "Pete Jackson",
"valuta": " EUR",
"customer": "Jim Manner",
"office": "123 Houston",
"UNID": "9D574D34B9140D3CC1257B8E002A487E"
}
}, {
"request": {
"responsible": "Jane Awesome",
"valuta": " EUR",
"customer": "Christian Slater",
"office": "503 New York",
"UNID": "2444DAA352E89A44C1257B8E002A487F"
}
}]
}
The datatables columns I have defined as followed:
'columns': [{
data: 'request.office',
'render': function(data) {
return data;
}
}, {
data: 'request.responsible',
'render': function(data) {
return data;
}
}, {
data: 'request.customer',
'render': function(data) {
return data;
}
}
]
Now I want to apply rowGrouping according the following example that I have found: http://live.datatables.net/migixiqi/1/edit
However it uses the rows for grouping and its seems the columns defined as dataSrc are considered as objects cause I get 'No group' returned as row group label.
How can I send in a real value as source in the rowgroup definition instead of the (expected) column value?
Perhaps I dont understand what you are hoping to do, but you can just pass the JSON path to the rowGroup.dataSrc exactly as you do with columns.data :
rowGroup: {
dataSrc: 'request.customer' //just a guess you want to group by custumer
},
http://jsfiddle.net/tgsz78jk/
PS: render() callbacks are unnecessary unless you actually need to do something special with a columns content, sort, filter or search behaviour.

How to create a custom intent and calling helper intent using Actionsdk?

Please find my action.json file content
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "testapp"
},
"intent": {
"name": "actions.intent.MAIN",
"trigger": {
"queryPatterns": [
"talk to Developer"
]
}
}
},
{
"name": "BUY",
"intent": {
"name": "com.example.sekai.BUY",
"parameters": [{
"name": "color",
"type": "SchemaOrg_Color"
}],
"trigger": {
"queryPatterns": [
"find some $SchemaOrg_Color:color sneakers",
"buy some blue suede shoes",
"get running shoes"
]
}
},
"fulfillment": {
"conversationName": "testapp"
}
}
],
"conversations": {
"testapp": {
"name": "testapp",
"url": "https://us-central1-samplejs2-id.cloudfunctions.net/testApp",
"fulfillmentApiVersion": 2,
"inDialogIntents": [
{
"name": "actions.intent.CANCEL"
}
]
}
},
"locale": "en"
}
Please find my index.js file content
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {actionssdk} = require('actions-on-google');
const app = actionssdk({debug: true});
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
app.intent('com.example.sekai.BUY', (conv, input) => {
console.log("Inside custom intent");
conv.ask('<speak>Hi! <break time="1"/> ' +
' The color you typed is' +
`<say-as >${input}</say-as>.</speak>`);
});
app.intent('actions.intent.MAIN', (conv, input) => {
conv.ask('<speak>Hi! <break time="1"/> ' +
'You are entering into samplejs application by typing ' +
`<say-as >${input}</say-as>.</speak>`);
});
app.intent('actions.intent.CANCEL', (conv) => {
conv.close(`Okay, let's try this again later.`);
});
app.intent('actions.intent.TEXT', (conv, input) => {
if (input === 'bye') {
return conv.close('Goodbye!');
}
conv.ask('<speak>You said, ' +
`<say-as >${input}</say-as>.</speak>`);
});
//exports.app = app;
console.log("global----------->",global);
exports.testApp = functions.https.onRequest(app);
Whenever I call the custom intent "BUY" using any color, instead of calling my custom intent it is calling "intent.Text". How to fix this issue?
While creating cloud function I have select JavaScript option.
For creating a custom intent, is these much updates is need in action.json?
Is there any option for creating custom intent?
How to call this helper content in the js file?
app.intent('ask_for_place', (conv) => {
conv.ask(new Place(options));
});
Custom intents are only triggered as welcome intents for "deep linking". Once the conversation has started, all conversational intents will be reported as TEXT.
So for the intent com.example.sekai.BUY you defined in your actions.json file, and if your Action was named something like "super store", then the following invocation would trigger that intent:
Hey Google, ask super store to find some blue sneakers
but once the conversation had started, asking "find some blue sneakers" would trigger a TEXT intent.
The Actions SDK with actions.json is primarily intended for use by systems that provide the natural language processing and just want to get the text after it has been converted from speech.
If you want more sophisticated natural language processing, where each phrase in the conversation will trigger a user-defined Intent, take a look at Dialogflow.

Structuring Normalized JSON response in Redux store and mapping to React component props

Just recently, our team began structuring our JSON payload in a normalized fashion. I am most used to working with nested data in React components and even in the reducer, but I see the benefits here (less connected components re-rendering, simplified reducer code, and easier tests) and I am excited to start using this approach. I do however, have some confusion with the state shape after my first try.
Let's start with the shape of the payload -
{
"data": {
"advisors": {
"allIds": [
2
],
"byId": {
"2": {
"active": true,
"avatar_url": null,
"country": "US",
"email": "demo#gmail.com",
"first_name": "George Michael",
"full_name": "George Michael Bluth",
"id": 2,
"last_name": "Bluth",
"time_zone": "US/Central"
}
}
},
"opportunities": {
"allIds": [
"100-3",
],
"byId": {
"100-3": {
"created": "Fri, 29 Sep 2017 20:00:40 GMT",
"program_id": 3,
"prospect_id": 100
}
}
},
"programs": {
"allIds": [
3
],
"byId": {
"3": {
"abbr": "CAP",
"end_date": null,
"funnel_id": 2,
"id": 3,
"launch_date": "Sat, 11 Mar 2017 00:00:00 GMT",
"name": "Certificate in Astral Projection",
"period_end": null,
"period_start": null,
"program_level_abbr": "NCC",
"school_id": 2,
"virtual": false
}
}
},
"prospects": {
"allIds": [
2,
],
"byId": {
"2": {
"advisor_id": 3,
"contact_attempt_count": 0,
"contact_success_count": 0,
"do_not_call": false,
"do_not_email": false,
"do_not_mail": false,
"email": "adavis.est#hotmail.com",
"first_name": "Antonio",
"id": 2,
"inactive": false,
"last_name": "Davis",
"phone": {
"area_code": "800",
"extension": "70444",
"number": "3575792"
},
"priority": 10.0,
"referred_by_prospect_id": null,
"third_party": false
},
}
}
},
"pagination": {
"page_number": 1,
"total": 251
}
}
The normalized payload is structured so that advisors, opportunities, programs, and prospects are siblings and not ancestors. They're all nested one level inside of "data".
Then, in my "prospects" reducer I initialize the prospects state as an object with the following keys: fetching, failure, and entities. The first two are UI data and entities will house the response (advisors, opportunities, programs, and prospects).
const initialState = {
fetching: false,
failure: false,
entities: null,
};
function prospects(state = initialState, action) {
switch (action.type) {
case constants.prospects.REQUEST_PROSPECTS:
return { ...state, fetching: true };
case constants.prospects.RECEIVE_PROSPECTS:
return Object.assign({}, state, {
fetching: false,
entities: action.data,
});
case constants.prospects.REQUEST_PROSPECTS_FAILURE:
return { ...state, fetching: false, failure: true };
default:
return state;
}
}
And now for the red flag that brought me here - my props and internal component state seem oddly structured. I mapStateToProps like so:
const mapStateToProps = state => ({
prospects: state.prospects,
});
This has resulted in me accessing advisors, opportunities, programs, and prospects like this:
this.props.fetching
this.props.failure
this.props.prospects.entities.advisors.allIds.length
this.props.prospects.entities.opportunities.allIds.length
this.props.prospects.entities.programs.allIds.length
this.props.prospects.entities.prospects.allIds.length
My understanding is that with a normalized approach things are typically housed under this.props.entities and ui data in this.props.ui. Is the problem that I am getting all this data back from my prospects action and reducer and not separate actions and reducers? I want to reducer the accessor chain in my components, because it's become very error prone and hard to read. Would it be better to query for each entity with separate XHRs and reducers?
I know there a lot of good resources on this approach including videos from DA. But I haven't found an answer to all of these questions in combination. Thanks!
Summary
I'm suggesting that you refactor your state to look like:
{
network: {
loading: false,
failure: false
},
advisors: { allIds, byId },
opportunities: { allIds, byId },
programs: { allIds, byId },
prospects: { allIds, byId },
}
To do this, you'll want a reducer for each key in the state. Each reducer will handle its portion of the normalized payload and otherwise ignore actions.
Reducers
Network.js:
function network(state = { loading: false, failure: false }, action) {
switch (action.type) {
case constants.REQUEST_PAYLOAD:
return { ...state, fetching: true };
case constants.RECEIVE_PAYLOAD:
return { ...state, fetching: false, failure: false };
case constants.prospects.REQUEST_PROSPECTS_FAILURE:
return { ...state, fetching: false, failure: true };
default:
return state;
}
}
Prospects.js:
function prospects(state = { allIds: [], byId: {} }, action) {
switch (action.type) {
case constants.RECEIVE_PAYLOAD:
// depending on your use case, you may need to merge the existing
// allIds and byId with the action's. This would allow you to
// issue the request multiple times and add to the store instead
// of overwriting it each time.
return { ...state, ...action.data.prospects };
default:
return state;
}
}
Repeat the prospects reducer for each other section of the state.
Note
I'm assuming your payload comes back in that fashion from a single API call, and that you're not stitching that together from separate calls for each sibling (advisors, opportunities, programs, and prospects).
Details
In order to store your payload in the store, I would recommend writing separate reducers that each handle a different part of the state returned by your API call.
For prospects, you should only store the prospects portion of the payload and throw out the rest.
So instead of...
case constants.prospects.RECEIVE_PROSPECTS:
return Object.assign({}, state, {
fetching: false,
entities: action.data,
});
You should do...
case constants.prospects.RECEIVE_PROSPECTS:
return {
...state,
fetching: false,
entities: action.data.prospects,
};
Then have a similar reducer for each of the other types of data returned by your API call. Each of these reducers will process the exact same actions. They'll only handle the portion of the payload that they care about, though.
Finally, in your mapStateToProps, state.prospects will only contain the prospect data.
As a side note--assuming I'm correct about the payload being delivered by a single API--I would rename your action constants to REQUEST_PAYLOAD, RECEIVE_PAYLOAD and REQUEST_PAYLOAD_FAILURE, or something equally generic.
One more suggestion: you can move your fetching and failure logic into a NetworkReducer that only has the job of managing success/failure/loading for the API request. That way, each of your other reducers only has to handle the RECEIVE case and can just ignore other actions.

Problems forming multiple collections and models in Backbone.js

I am having issues creating a multiple collections and models. I am trying to form a basic Backbone.js Collection.
I am having two issues.
Looking at the console log of whats passed through to the view I can see the Event model is not actually being created (There is no id: 'No ID' in the attributes). I know that the collection Months forms Month models but I cannot explain why Events is not forming Event models.
Collections are only formed when the Months collection is initialized with reset:true. I have no idea how to explain this
I have a collection of Months of the model Month. The model Month has a collection of Events of the model Event as follows:
$(function(){
var Event = Backbone.Model.extend({
defaults: {
id: 'No ID',
description: 'No description',
startdate: 'No startdate'
}
});
var Events = Backbone.Collection.extend({
model: Event,
parse: function(response) {
return response;
}
});
var Month = Backbone.Model.extend({
defaults: function() {
return {
events: new Events(this.get('events'))
};
}
});
var Months = Backbone.Collection.extend({
model: Month,
url : '/api/v1/calendar/2014',
initialize: function(){
this.fetch({
reset: true
});
},
parse: function(response) {
if ('months' in response) {
console.log('Months Collection: ' + response);
return response.months;
} else {
console.error('No months found.');
}
}
});
window.Calendar = new Months();
});
An example of the JSON I am trying to interpret here:
{
"status": "success",
"year": 2014,
"months": [
{
"month": 1,
"events": [
{
"title": "None",
"startdate": "2014-01-23",
"enddate": "None",
"description": "description 1"
}
]
},
{
"month": 12,
"events": [
{
"title": "None",
"startdate": "2014-12-24",
"enddate": "None",
"description": "description 2"
},
{
"title": "None",
"startdate": "2014-12-25",
"enddate": "None",
"description": "description 3"
}
]
}
]
}
Any help would be appreciated
Thank you
Remove the id from the defaults altogether.
The id is a representation of id's that are present in the back end; if the id on a Backbone model is present, then the library assumes that you are working with a model that exists in the back end (and has a corresponding id there).
If the model was not persisted to a data source yet, then the model doesn't have an id and should therefore be empty. Functionally, backbone will in that case perform a POST instead of PUT on save(), when the model does not have an id defined.
{reset:true}fires an explicit reset event on the entire collection (instead of the default set per model), but this should not be required to fetch the models from the server.
For getting access to the models that were fetched, you should implement a callback function though, which takes the response of the model's fetch method as parameter. When you call this.fetch(), a promise is returned, which you have yet to process.

How to get deeper in a JSON object using angularJS?

I am using AngularJs to get some information inside this JSON object, specifically the author's first and last name:
{
"bookid": "1",
"title": "Spring In Action",
"publisher": "Manning Publications Co.",
"isbn": "978-1-935182-35-1",
"owner": "Catalyst IT Services",
"publishyear": "2011",
"image": "C:/imagefolder/spring-in-action.jpg",
"description": "Totally revised for Spring 3.0, this book is a...",
"author": [
{
"authorid": "1",
"firstname": "Craig",
"lastname": "Walls",
"description": "Craig Walls has been professionally developing software for over 17 years (and longer than that for the pure geekiness of it). He is the author of Modular Java (published by Pragmatic Bookshelf) and Spring in Action and XDoclet in Action (both published by (...)"
}
],
"media": [
],
"tags": [
{
"tagid": "1",
"tagname": "Java"
},
{
"tagid": "5",
"tagname": "Spring"
}
],
"copies": [
{
"bookcopyid": "2",
"location": "Beaverton",
"status": "available"
}
]
}
The code I have right now is (which was provided by bosco2010 in this plunker (http://plnkr.co/edit/GbTfJ9)):
var app = angular.module('app', []);
app.factory('JsonSvc', function ($http) {
return {read: function(jsonURL, scope) {
return $http.get(jsonURL).success(function (data, status) {
scope.data = data.author;
});
}};
});
app.controller('MainCtrl', function($scope, JsonSvc) {
JsonSvc.read('data.json', $scope).then(function () {
$scope.nestedObj = $scope.data;
});
$scope.name = "world";
});
To get the first and last name, you'll need to reference author[0].firstname and author[0].lastname.
var app = angular.module('app', []);
app.factory('JsonSvc', function ($http) {
return {read: function(jsonURL) {
return $http.get(jsonURL);
}};
});
app.controller('MainCtrl', function($scope, JsonSvc) {
// The return object from $http.get is a promise. I used .then()
// to declare $scope.nestedObj after the http call has finished.
JsonSvc.read('data.json').then(function (data) {
console.log(data.data);
$scope.nestedObj = data.data.level1;
});
// ensure app is working
$scope.name = "world";
// Using nested obj within declared scope var doesn't work
// Uncomment below to break whole app
// Using nested obj in a function works but throws TypeError
// Declaring $scope.data.level1.level2 = [] first helps here
$scope.getLen = function () {
return $scope.nestedObj ? $scope.nestedObj.level2.length : ''; // return an empty string if the callback didn't happen yet
};
});
In short, it is incorrect to use both the success() function and also the then() function of the promise returned by the $htttp service.
Moreover, it is wrong to pass your scope as a parameter to your service and try to modify it there.
if you need to communicate between the service and a controller directly, you can use either $rootScope, $broadcat, or both.
I patched up your code, and now it works.
Plunk:
http://plnkr.co/edit/PlJZZn?p=preview