Breeze Entity property name mapping - angularjs

I have a server JSON object that looks like this:
{
"Id": 58,
"ContentTypeId": "0x010800CC86F2447E566746907284C1DE2DE7F5",
"Title": "Rework and Drop Test",
"Priority": "(2) Normal",
"Status": "Waiting on someone else",
"PercentComplete": 0,
"AssignedToId": 93,
"Body": "Some text",
"StartDate": "2015-03-06T05:00:00Z",
"DueDate": "2015-03-13T05:00:00Z"
}
And I am using Angular-Gantt which expects its objects to have the property from where the object above has StartDate and then to where the object above has DueDate. Is there an easy way to map the name of a property on the Breeze entity to the property on JSON object. For example, Kendo UI's model definition has a fromField property that I could use to perform this mapping.
to: {
fromField: 'DueDate'
}
I tried the following in breeze.js
StartDate: {
type: breeze.DataType.DateTime,
name: 'from'
}
and
from: {
type: breeze.DataType.DateTime,
name: 'StartDate'
}
But neither of those worked. I also tried nameOnServer with the same lack of results. I know that I can generate a naming convention object but the idea of hand writing the mapping of these fields on both the client and the server side seems silly to me to have two giant switch statements that do essentially the same thing in reverse and it does not strike me as maintainable. Any suggestions? I hope I am just missing something in the documentation.
I'm using v.0.2.4 of breeze.labs.dataservice.abstractrest.js and v.0.2.3 of breeze.labs.dataservice.sharepoint.js.
Edit:
I did just update to the latest builds and I am still seeing the exact same thing.
Using a naming convention object with the nameOnServer property on my entity works. But I'd rather not do it this way.
function AngularGanttNamingConvention() {
return new breeze.NamingConvention({
name: 'angularGanttNamingConvention',
clientPropertyNameToServer: clientPropertyNameToServer,
serverPropertyNameToClient: serverPropertyNameToClient
});
function clientPropertyNameToServer(propertyName) {
return fixUpClientProperty(propertyName);
}
function fixUpClientProperty(propertyName) {
var newName;
switch (propertyName) {
case "DueDate":
newName = "to";
break;
case "to":
newName = "DueDate";
break;
case "StartDate":
newName = "from";
break;
case "from":
newName = "StartDate";
break;
case "Title":
newName = "name";
break;
case "name":
newName = "Title";
break;
default:
newName = propertyName;
}
return newName;
}
function serverPropertyNameToClient(propertyName) {
return fixUpClientProperty(propertyName);
}
}
AngularGanttNamingConvention().setAsDefault();

Related

Angularjs: Many-to-Many with Through Relationship

In my app I have Students who can Enroll in Classes.
An Enrollment records the start and end dates along with a reference to which Class.
The Class has details like a class name and description.
Student (1) - (N) Enrollment (1) - (1) Class
Therefore
Student (1) - (N) Class
This is what I would like the object to look like.
{
"studentId": 1,
"name": "Henrietta",
"enrolledClasses": [
{
"enrollmentId": 12,
"startDate": "2015-08-06T17:43:14.000Z",
"endDate": null,
"class":
{
"classId": 15,
"name": "Algebra",
"description": "..."
}
},
"enrollmentId": 13,
"startDate": "2015-08-06T17:44:14.000Z",
"endDate": null,
"class":
{
"classId": 29,
"name": "Physics",
"description": "..."
}
}
}
But my RESTful API cannot (easily) output the full nested structure of the relationship because it's ORM can't do Many to Many with Through relationships. So I get the student and their multiple enrollments, but only a reference to the class for each, not the details. And I need to show the details, not just an Id.
I could wait for the student object to be available and then use the references to make additional calls to the API for each classId to get details, but am not sure how, or even if, to then integrate it with the student object.
This is what I have so far.
function findOne() {
vm.student = StudentsResource.get({
studentId: $stateParams.studentId
});
};
This is what I get back from my API
{
"studentId": 1,
"name": "Henrietta",
"enrolledClasses": [
{
"enrollmentId": 12,
"startDate": "2015-08-06T17:43:14.000Z",
"endDate": null,
"class": 15
},
"enrollmentId": 13,
"startDate": "2015-08-06T17:44:14.000Z",
"endDate": null,
"class": 29
}
}
And for the class details I can them one at a time but haven't figured out how to wait until the student object is available to push them into it. Nor how to properly push them...
{
"classId": 15,
"name": "Algebra",
"description": "..."
}
{
"classId": 29,
"name": "Physics",
"description": "..."
}
Question
Is it good practice to combine the student and class(es) details through the enrollments, as one object?
If so, whats the most clear way to go about it?
If not, what is another effective approach?
Keep in mind that the app will allow changes to the student details and enrollments, but should not allow changes to the details of the class name or description. So if Angular were to send a PUT for the object, it should not try to send the details of any class, only the reference. This would be enforced server side to protect the data, but to avoid errors, the client shouldn't try.
For background, I'm using SailsJS and PostgreSQL for the backend API.
So basically on the Sailsjs side you should override your findOne function in your StudentController.js. This is assuming you are using blueprints.
// StudentController.js
module.exports = {
findOne: function(req, res) {
Student.findOne(req.param('id')).populate('enrollments').then(function(student){
var studentClasses = Class.find({
id: _.pluck(student.enrollments, 'class')
}).then(function (classes) {
return classes
})
return [student, classes]
}).spread(function(student, classes){
var classes = _.indexBy(classes, 'id')
student.enrollments = _.map(student.enrollments, function(enrollment){
enrollment.class = classes[enrollment.class]
return enrollment
})
res.json(200, student)
}).catch(function(err){
if (err) return res.serverError(err)
})
}
}
// Student.js
module.exports = {
attributes: {
enrollments: {
collection: 'enrollment',
via: 'student'
}
// Rest of the attributes..
}
}
// Enrollment.js
module.exports = {
attributes: {
student: {
model: 'student'
}
class: {
model: 'class'
}
// Rest of the attributes...
}
}
// Class.js
module.exports: {
attributes: {
enrollments: {
collection: 'enrollment',
via: 'class'
}
// Rest of the attrubutes
}
}
Explanation:
1. the _.pluck function using Lo-dash retuns an array of classIds and we find all of the classes that we wanted populated. Then we use the promise chain .spread(), create an array indexed by classid and map the classIds to the actual class instances we wanted populated into the enrollments on the student returned from Student.findOne().
2. Return the student with enrollments deep populated with the proper class.
source: Sails.js populate nested associations

how to add a complextype object dynamically to an array

We have created an array of complextype(Carrier field) objects. See below metadata
{ shortName : 'Person',
namespace : 'Demo',
autoGeneratedKeyType : breeze.AutoGeneratedKeyType.Identity,
"dataProperties": [
{
"name": "carriers",
"complexTypeName":"Carrier:#Test",
"isScalar":false
}]
}
The Carrier entity is defined as below:
{
"shortName": "Carrier",
"namespace": "Test",
"isComplexType": true,
"dataProperties": [
{
"name": "Testing",
"isScalar":true,
"dataType": "String"
}
]
}
We have the following matching data for the above entities:
{
carriers: [
{
Testing : 'InputBox1'
},
{
Testing : 'InputBox2'
}
]
}
We are trying to dynamically add the complextype object(Carrier) to the above carriers array by using the following approach:
var test = {
"Testing" : "Test"
};
var result = manager.createEntity('Carrier', test);
The above code throws an exception(undefined is not a function) inside breeze.debug.js at line number 12457(see below code)
entity = entityType.createEntity(initialValues);
The exception is thrown since the complextype entity does not have 'createEntity' function in it.
What are we missing here?
Excellent question - Sorry I didn't have a chance to address this earlier.
When adding a complexType object you need to use the createInstance() method instead of the createEntity.
var thisEntityType = manager.metadataStore.getEntityType('Carrier');
var thisEntity = thisEntityType.createInstance(initialValues);
Basically you get the complexType and then create an instance of it using the values you want assigned. Keep in mind the initial values should be a hash object of course. Often I will include a helper function to do this for me like this -
function createComplexType(entityType, constructorProperties) {
var thisEntityType = manager.metadataStore.getEntityType(entityType);
var thisEntity = thisEntityType.createInstance(constructorProperties);
return thisEntity;
}

Merge objects with different values using Angularjs or Underscore js

I'm trying to merge two objects into a single multidimensional object for use in Angularjs controller by the 'unique_id'. (Note I also have Underscore Js added in).
Object #1 example:
[
{ "unique_id": "001", "title": "Putting Green Challenge - Motion Depends on Force and Mass" },
{ "unique_id": "002", "title": "Molecules to Organisms: Frog Life Cycle" }
]
Object #2 example (has MANY more rows than object 1..):
[
{
"ab_id": "76153F02-29F3-11D8-95EA-951BF95D9AEF",
"unique_id": "001",
"title": "How Speed Relates to Energy",
"state": "NY",
"document_title": "Core Curriculum",
"grade_code": "K-4",
"grade_descr": "Elementary",
"state_id": "1.S2.3a",
"state_text": "Use appropriate \"inquiry and process skills\" to collect data"
},
{
"ab_id": "7980A762-29F3-11D8-BD14-861D7EA8D134",
"unique_id": "001",
"title": "How Speed Relates to Energy",
"state": "NY",
"document_title": "Core Curriculum",
"grade_code": "5-8",
"grade_descr": "Intermediate",
"state_id": "1.S3.2d",
"state_text": "formulate and defend explanations and conclusions as they relate to scientific phenomena"
}
]
My Controller:
abApp.controller("abEE", function(abService, $http, $scope, $q, _) {
var abApp = this;
$scope.abData = $http.get('/data/ab_activities.json', {
cache: false
});
$scope.eeData = $http.get('/activities/eedata', {
cache: false
});
$q.all([$scope.eeData, $scope.abData]).then(function(values) {
var val = ??? This is where I want to merge the objects into one big multidimensional object..
});
Here is the output of console.dir(values);
0 Object { data=[28], status=200, config={...}, more...}
1 Object { data=[743], status=200, config={...}, more...}
This is the desired output I'd like to try and get:
[
{ "unique_id": "001", "title": "Putting Green Challenge - Motion Depends on Force and Mass", "alignments": [{"ab_id": "76153F02-29F3-11D8-95EA-951BF95D9AEF","unique_id": "001","title": "How Speed Relates to Energy",...}, {"ab_id": "7980A762-29F3-11D8-BD14-861D7EA8D134", "unique_id": "001", "title": "How Speed Relates to Energy",...}]
]
Edit
after you updated the question, i created this plunker
hopes it's what you meant
To merge all objects by unique_id
var unions = {};
$q.all([$scope.eeData, $scope.abData]).then(function(values)
{
for (var i = 0; i< values.length; i++)
{
var value = values[i];
if (!unions[value.unique_id])
{
unions[value.unique_id] = {};
}
angular.extend(unions[value.unique_id], value);
}
});
// Do somthing with 'unions'
...
If you could switch to use lodash instead of underscore, it can be achieved like this:
var val = _.values(_.merge(_.indexBy(values[0].data, 'unique_id'), _.indexBy(values[1].data, 'unique_id')));
The underscore doesn't have _.merge(), you have to loop through each property without it.
I don't think angular or underscore have this kind of functionality. I would do something like the following pseudo-code:
tempObject = {}
for object in objectArray
if tempObject[object.unique_id] isnt undefined
tempObject[object.unique_id] = object
else angular.extend(tempObject[object.unique_id], object) // or the other way around depending on your preference
resultingArray = []
for object, key of tempObject
resultingArray.push(object)
You will have to run the for object in objectArray for both the returned arrays but that should work and is probably more efficient than most merge algorithms as at most it will loop through each returned arrays twice.

Breeze Mongo Angular autoGeneratedKey error

I'm using Breeze with Angular and MongoDB.
I included all the necessary services and scripts to make sure breeze works with angular and MongoDB.
However, when I try to save my changes I get the following error on the server:
ObjectIds and Guids are the only autoGenerated key types that Breeze currently supports, not: undefined
This error occurs in the mongoSaveHandler.js file of the mongobreeze module:
var keyDataType = entityType.keyDataType;
if (keyDataType === "Guid") {
e._id = createGuid();
} else if (keyDataType == "MongoObjectId") {
// instead of omitting the _id and having mongo update it, we want to set it ourselves so that we can do
// fk fixup before going async
e._id = new ObjectID();
} else {
that._raiseError(new Error("ObjectIds and Guids are the only autoGenerated key types that Breeze currently supports, not: " + keyDataType));
return;
}
I made sure that the id of my object is a mongo id:
function addVisit() {
addType({
name: 'Visit',
dataProperties: {
id: { type: DT.MongoObjectId },
pain: { type: ID },
paper: {type: ID},
consistency: {type: ID}
}
});
}
But indeed when I log the entityType object it has no property keyDataType?
I can get everything to work if I just remove the error. Then my inserted objects look like this in MongoDB:
{ id: 5350d4e704a02e1f04000000,
pain: 50,
consistency: 50,
date: Fri Apr 18 2014 08:31:51 GMT+0100 (WEST),
_id: 5350d4e7101b04a9560e660a },
Meaning they have 2 ids?
When I try to query the database I get a nice response:
[
{
"id": "535052f504a02e79c6000000",
"pain": 50,
"consistency": 50,
"_id": "535052f6f672174a4dffffd4"
},
{
"id": "5350d1bb04a02e4e56000000",
"pain": 50,
"consistency": 50,
"date": "2014-04-18T07:18:19.616Z",
"_id": "5350d1bb101b04a9560e6606"
},
{
"id": "5350d2c104a02e595c000000",
"pain": 50,
"consistency": 50,
"date": "2014-04-18T07:22:41.696Z",
"_id": "5350d2c1101b04a9560e6607"
},
]
But somehow Breeze is unable to import this properly and I get a circular dependency.
Could this have something to do with the double ID's?
Where did you get DT.MongoObjectId from? That isn't listed in the breeze docs as a supported data type so it is returning undefined as a type. If you are properly generating the Id why not just use a string if it is immutable?
id : { type: DT.String }
Try to set a naming convention that would convert breeze's "id" field into mongo's "_id" and vice versa. It will eliminate double ID's
Here's the code for client side:
var convention = new breeze.NamingConvention({
serverPropertyNameToClient: function (serverPropertyName) {
switch (serverPropertyName) {
case '_id':
return 'id';
default :
return serverPropertyName;
}
},
clientPropertyNameToServer: function (clientPropertyName) {
switch (clientPropertyName) {
case 'id':
return '_id';
default:
return clientPropertyName;
}
}
});
convention.setAsDefault();

ExtJs root node

What is the root property value if I get a Json like that:
{
"status": {
"status": 0,
"msg": "Ok",
"protocolversion": "extjs.json"
},
"value": {
"table": [
[
"admin",
"Administrator",
""
],
[
"test",
"Test",
""
]
],
"total": 2
}
}
The data will be displayed in a gridpanel, 1 row is admin, 1 row is test, etc.
Tried:
value, value.table
How to get this to work?
value.table is correct for the root property, but you are using a json format that I don't think Ext is set up to handle by default. It has a reader for json that is used for an array of objects, not for an nested arrays of field values with no object mapping information.
If you have to use that format, you will either need to create your own readers/writers or just use Ext.Ajax.request(), and in the callback, parse the nested array into objects. Something like:
Ext.Ajax.request({
url: 'path.com/to/content',
success: function (response, operation) {
var data = Ext.JSON.decode(response.responseText);
var fields = data.value.table;
var records = [];
Ext.Array.each(fields, function (fieldArray, fieldIndex) {
Ext.Array.each(fieldArray, function(fieldValue, valueIndex) {
//Create record object if it doesn't exist
var record = records[valueIndex] || {};
//Create a switch statement based on the array index to set fields
switch(fieldIndex) {
case 0:
record.User_id = fieldValue;
break;
case 1:
record.Username = fieldValue;
break;
}
});
});
//Add the objects to the empty store
store.add(records);
}
});
That's not a production solution by any means since it doesn't handle that empty string at the end of your list or the case that you get a jagged array of arrays per field which I can't imagine what to do with. If it's within your control or influence, I would suggest using a format more like what Ext suggests so you can use the built in json proxy/reader/writer or go crazy with it and implement ext's remote procedure call format:
{
"success": true,
"message": "Success",
"data": [
{
"User_id": "admin",
"Username": "Administrator"
}, {
"User_id": "admin",
"Username": "Administrator"
}
]
}
In above example "value" is root property. But for JSON reader it's a property name (or a dot-separated list of property names if the root is nested).
so you can assign into your field you need following.
fields:['table.admin','table.test']

Resources