I am working on a Rally App I am using a Store to pull data from the Portfolio/Feature Modle.
This is working as expected. I want to convert what the lisener returns to an arry
The issue I am facing is the array is just retruning the Object and I need the data from the Object Property.
Results of the array look like this
["F1870", "25343 - some project name ", "", Object, Object, Mon Apr 27 2015 02:00:00 GMT-0400 (Eastern Daylight Time)]
The first Objects Value should be John Smith.
John Smith Sits in the propery of the
Object
0: "F1870"
1: "25343 - Some "
2: ""
3: Object
_p: "0"_ref: "blah Balh"
_refObjectName: "John Smith"
_refObjectUUID: "blah blah"
_type:
Owner[_refObjectName] what i need to get and i am lost.
******Edited To add more detials****
The store returns values look like this
data: Object
FormattedID:F1223
Name: Some project
Description: Blah blah blah
Owner: Object
_p:
_ref:
_refObjectName: John Smith
I need the array to return
FormattedID:F1223
Name: Some project
Description: Blah blah blah
Owner: John Smith
Here is the code i have so far.
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function () {
console.log("App Launched")
//App Calls the portfolio feature data store
this._getfeaturedatastore();
},
//Get the portfolio feature data from Rally
_getfeaturedatastore: function(){
var getfeaturedata = Ext.create('Rally.data.wsapi.Store', {
model: 'PortfolioItem/Feature',
autoLoad: true,
//Create Fillter for the Store
filters: [
{
property: 'State.Name',
value: 'Story Definition',
}
],
listeners: {
load: function(getfeaturedatastore, getfeaturedatadata, success) {
console.log("Got Feature Data Woot",getfeaturedatastore, getfeaturedatadata, success)
this._displayFeatureCard(getfeaturedata);
},
scope: this
},
fetch: ['State', 'Name', 'Description', 'Owner', 'Parent','PlannedStartDate','FormattedID','Tags']
});
},
_displayFeatureCard: function(getfeaturedata){
var MAX_NAME_LEN = 115;
var name,i,theMarkup, description, owner, parent, plannedstartdate, formattedid;
var data =[];
getfeaturedata.each(function(record){
var recordArray = [
record.get("FormattedID"),
record.get("Name"),
record.get("Description"),
record.get("Owner"),
record.get("Parent"),
record.get("PlannedStartDate")
];
data.push(recordArray);
console.log(recordArray)
});
The clues as to what data you have access to and how to do it, is accessible via the WebServices documentation ( go to the help link accessible via your avatar in the top right hand corner)
Any artifact that is held in the Rally database can come back to you looking like: a string, a number, an object or a collection (of objects).
In the case of the 'Owner' of a portfolio item, it is an object of type User. The contents of the object describe the owner and not just providing the name. As it is an object, you have to do record.get("Owner") to get the object and then do record.get("Owner").Name to get the name of the owner.
FormattedID comes back as a string, so you just need to do record.get("FormattedID") to get the text.
You can get all the records from the store using the getRange method and then get all the data from each record using the getData method.
listeners: {
load: function(store) {
var data = _.map(store.getRange(), function(record) {
return record.getData();
});
var feature1 = data[0],
ownerName = feature1.Owner._refObjectName;
}
}
This example also uses the lodash map function to reduce the lines of code necessary. At this point data will be an array of plain old javascript objects with the data from the store.
Related
Also asked on Sencha's site here
My data model's "serialize" function is not called when I call
model.set("<fieldName>", <newValue>);
Here's a fiddle
I'm pretty unclear on why the serialize function isn't called...am I missing something, or is this a bug?
(And here's the code from the fiddle)
Ext.application({
name : 'Fiddle',
requires: [
"Ext.data.Store"
],
launch : function() {
var store = Ext.create("Ext.data.Store", {
data: [{Id: 0, Name: "Bill", Props: "{foo: 2, bar:{pan:5}}"}],
fields:[
{name: "Id", type: "int"},
{name: "Name", type: "string"},
{name: "Props",
convert: function(value, record){
console.log("called convert");
return Ext.JSON.decode(value);
},
serialize: function(value, record){
alert("never getting called!! :(");
console.log("sure, i'll put log here too..not getting called though");
return Ext.JSON.encode(value);
}
}
]
});
console.log(store.getAt(0));
var rec = store.getAt(0);
var newProp = {rec:"junk", foo: "orange"};
console.log(newProp);
rec.set("Props",newProp);
}
});
Mappings from source content (JSON/XML) to business model (Ext.data.Model) are not automatically created in ExtJS's data model system. As such, another step is needed to produce this relationship using mapping/associationsor something similar.
I.e. The data model doesn't store the original JSON to read/write from, which is fine for most cases. When a JSON string needs to be updated via ExtJS, one solution is to, on the model, set
convertOnSet
to false, allowing for custom manipulation of the JSON string via extract/update functions on the data model.
I have a factory, which goes into a controller, and I am trying to get data from that display on an HTML page. I am having trouble specifying an Object's pathway however.
My Factory:
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
apis:
[{
accounts: [
{
v1: [
{
uri: Head+"/v1/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}],
v2: [
{
uri: Head+"/v2/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}]
}
],
customers: [
{
v1: [
{
uri: Head+"/v1/customers/",
item1: "CustomerName",
item2: "CustomerID",
item3: "CustomerEmail"
}]
}
]
}]
};
});
My Controller:
app.controller('APIController', function($scope, APIMethodService) {
$scope.title = "API";
$scope.apiList = APIMethodService;
$scope.accountList = $scope.apiList.accounts.v1;
$scope.accountList2 = $scope.apiList[0][0];
});
My HTML
<div ng-controller="APIController">
<div id="api" class="row">
<div class="col-xs-12">
<div class="row" style="font-size:20px">
{{title}} Page!
<table class="table table-striped">
<tr ng-repeat="api in apiList | orderBy:'uri' | filter:search">
<td>{{api.uri}}</td>
<td>{{api.item1}}</td>
<td>{{api.item2}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
The errors I get are in regards to the Controller trying to parse out the individual objects I wish to grab, like accounts or customers, and then any version v#, they may have.
So it will say something such as
TypeError: Cannot read property 'v1' of undefined
I just need some help specifying the proper pathways into my factory service.
You have a few problems. First, you are referring to the object returned from the factory incorrectly. APIMethodService is the factory that you're injecting, so you need to first reference the object that that factory is returning like this:
APIMethodService.apis
This will give you your entire JSON object.
From there, the rest of your object is made up of arrays of objects, so referring to 'v1' won't do you any good. You need to specify an index instead. If you want v1, you'll need:
APIMethodService.apis[0].accounts[0].v1
This will give you the v1 array, which again is an array of objects.
Customers would be:
APIMethodService.apis[0].customers[0].v1
The first problem you have is that the factory returns an object with a single property called apis. So basically this $scope.apiList.accounts.v1 should be $scope.apiList.apis.accounts.v1. Bu that's not all as this won't either work since dotting(.) into apis is an array you'd have to use the index. In this case it would be $scope.apiList.apis[0] and then you could .accounts[0].v1 which is also an array containing a single object.
Now if you can I would suggest to you that you'd change how you represent this data structure.
This is how you could do it.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: ["AccountNumber","MoneyInAccount"]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
And then it's just a matter of dotting into your APIMethodService-object like APIMethodService.accounts.v1.items[0] if you want the AccountNumber method name.
Constructing your url could then be done like this.
var baseUrl = APIMethodService.accounts.v1.uri; // 'api.example.com'
var url = baseUrl + APIMethodService.accounts.v1.items[0]; // 'AccountNumber'
// url = "api.example.com/v1/accounts/AccountNumber"
Again, this is one way you could do it but this can be further enhanced upon. The examples I provided are simply for demo purposes and this is not in any way the only way to do it.
Expanding upon recieved comments/questions your service (and data representation) could now look like this.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: [
{
name:'AccountNumber',
description:'Show the account number'
},
{
name:'AccountOwner',
description:'Show information about the owner of the account'
},
{
name:'MoneyInAccount',
description:'Show money in the Account'
}
]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
// Get descriptions
var accountNumberDescription = APIMethodService.accounts.v1.items[0].description; // 'Show the account number'
var accountOwnerDescription = APIMethodService.accounts.v1.items[1].description; // 'Show information about the owner of the account'
var moneyInAccountDescription = APIMethodService.accounts.v1.items[2].description; // 'Show money in the Account'
By using objects with properties like this it's alot easier to understand what you are trying to do. With arrays with indexes you'd have to know or take a look at the source to see what's going on. Here, someone viewing your code they can instantly understand that it is the description you are getting.
While using the RESTAdapter, I have an Organization model which is to be embedded in the response. It appears that the default implementation of the Ember.RESTAddapter sends the id, using the same model name, but not as an object (this currently 'works'):
POST/PUT api/v1/item/{id}
{
"item" {
id: "1029383829"
...
organization: "26044097612186763401268824297"
}
}
I have consulted the documentation, and found that the mixin DS.EmbeddedRecordsMixin should do this, coupled with declaring embedded: "always" on the attrs or the Serializer:
models/item.js
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {embedded: "always"})
});
serializers/item.js:
var ItemSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
organisation: {serialize: "id", embedded: "always"}
}
}
);
However, when deserializing records, Ember Data complains, saying that it expected an object, but just got a string:
Assertion Failed: Expected an object as data in a call to push for
app#model:organization: , but was 26044097612186763401268824297
Ultimately, I would prefer a system, likened to sideloading, wherein an additional attribute, post-fixed "_id", describes the corresponding id of an embedded record:
{
"item": {
id: 1,
name: "name",
organization_id: "26044097612186763401268824297"
...
}
}
How can I allow serializing and deserializing embedded id sideloading for my Organization model?
You aren't actually embedding the record, you're just specifying the id, in that case you should mark it as async.
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {async: true})
});
And remove your embedded records implementation.
I'm trying to create a nested, relational backbone project but I'm really struggling. The rough idea of what I'm trying to do is shown below but I was under the impression upon calling fetch() on Client, a number of bookings would automatically be created based on the bookings being returned as JSON.
The format of my JSON can be seen beneath the outline of the MVC:
/****************************************************
/* CLIENT MODEL - Logically the top of the tree
/* has a BookingsCollection containing numerous Booking(s)
/* Client
/* -Bookings [BookingsCollection]
/* -Booking [Booking]
/* -Booking [Booking]
/*****************************************************/
var Client = Backbone.RelationalModel.extend({
urlRoot: '/URL-THAT-RETURNS-JSON/',
relations: [
{
type: Backbone.HasMany,
key: 'Booking',
relatedModel: 'Booking',
collectionType: 'BookingsCollection'
}
],
parse: function (response) {
},
initialize: function (options) {
console.log(this, 'Initialized');
}
});
var Booking = Backbone.RelationalModel.extend({
initialize: function (options) {
console.log(this, 'Initialized');
}
});
var BookingsCollection = Backbone.Collection.extend({
model: Booking
});
Any help outlining what I'm doing wrong would be massively appreciated.
Thanks
EDIT
Thanks for taking the time to post the feedback, it's exactly what I was hoping for.
Is it the case that the JSON physically defines the actual attributes of models if you don't go to the effort of setting attributes manually?
In other words, if the JSON I get back is as you have suggested above, would Backbone simply create a Client object (with the 4 attributes id, title, firstname & surname) as well as 2 Booking objects (each with 4 attributes and presumably each members of the BookingsCollection)?
If this is the case, what is the format for referencing the attributes of each object? When I set up a non-backbone-relational mini-app, I ended up in a situation whereby I could just reference the attributes using Client.Attribute or Booking[0].EventDate for example. I don't seem to be able to do this with the format you have outlined above.
Thanks again.
The JSON being returned is not what Backbone or Backbone-Relational is expecting by default.
The expectation of Backbone and Backbone-Relational is:
{
"id": "123456",
"Title":"Mr",
"FirstName":"Joe",
"Surname":"Bloggs",
"Bookings": [
{
"id": "585462542",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
},
{
"id": "585462543",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
}
]
}
To use your response, you need to create a parse function on the Client model that returns the structure I've posted above.
A jsFiddle example of your model definitions working with my example JSON: http://jsfiddle.net/edwardmsmith/jVJHq/4/
Other notes
Backbone expects ID fields to be named "id" by default. To use another field as the ID for a model, use Model.idAttribute
The "key" for the Bookings Collection I changed to "Bookings"
Sample Code:
Client = Backbone.RelationalModel.extend({
urlRoot: '/URL-THAT-RETURNS-JSON/',
relations: [
{
type: Backbone.HasMany,
key: 'Bookings',
relatedModel: 'Booking',
collectionType: 'BookingsCollection'
}
],
parse: function (response) {
},
initialize: function (options) {
console.log(this, 'Initialized');
}
});
Booking = Backbone.RelationalModel.extend({
initialize: function (options) {
console.log(this, 'Initialized');
}
});
BookingsCollection = Backbone.Collection.extend({
model: Booking
});
myClient = new Client( {
"id": "123456",
"Title":"Mr",
"FirstName":"Joe",
"Surname":"Bloggs",
"Bookings": [
{
"id": "585462542",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
},
{
"id": "585462543",
"EventName": "Bla",
"Location":"Dee Bla",
"EventDate":"November 1, 2012"
}
]
});
console.log(myClient);
Post Edit
Yes, the JSON pretty much defines the attributes of the model. You can use a combination of parse(), defaults, and validate() to better control what attributes are valid and allowed.
The canonical way of reading and setting properties on a Backbone Model is through the get(), escape(), and set() functions.
set is especially important as this does a bunch of housekeeping, such as validating the attribute and value against your validate function (if any), and triggering change events for the model that your views would be listening for.
In the specific case of the situation in this answer, you might
myClient.get('Title'); // => "Mr"
myClient.get('Bookings'); //=> an instance of a BookingsCollection with 2 models.
myClient.get('Bookings').first().get('Location'); //=> "Dee Bla"
myClient.get('Bookings').last().get('Location'); //=> "Dee Bla"
myClient.get('Bookings').first().set({location:"Bora Bora"});
myClient.get('Bookings').first().get('Location'); //=> "Bora Bora"
myClient.get('Bookings').last().get('Location'); //=> "Dee Bla"
I have two backbone models, loaded from server:
var Model = Backbone.Model.extend({});
var SubModel = Backbone.Model.extend({});
var SubCollection = Backbone.Collection.extend({
model: SubModel
});
var m = new Model();
m.fetch({success: function(model)
{
model.submodels = new SubCollection();
model.submodels.url = "/sub/" + model.get("id");
model.submodels.fetch();
}});
So, the server has to send two separate responses. For example:
{ name: "Model1", id: 1 } // For Model fetch
and
[{ name: "Submodel1", id: 1 }, { name: "Submodel2", id: 2 }] // For Submodel collection fetch
Is there a way to fetch a Model instance with Submodel collection at once, like:
{
name: "Model1",
id: 1,
submodels: [{ name: "Submodel1", id: 2 }, { name: "Submodel1", id: 2 }]
}
To be able to do that is up to your back-end - it doesn't really have anything to do with Backbone.
Can you configure your back-end technology to return related models as nested resources?
If your back-end is Rails, for instance, and your models are related in ActiveRecord, one way of doing this is something like
respond_to do |format|
format.json { render :json => #model.to_json(:include => [:submodels])}
end
What back-end technology are you using?
Edit:
Sorry, misunderstood the gist of your question, once you've got your back-end returning the JSON in the proper format, yeah, there are things you need to do in Backbone to be able to handle it.
Backbone-Relational
One way to deal with it is to use Backbone-Relational, a plugin for handling related models.
You define related models through a 'relations' property:
SubModel = Backbone.RelationalModel.extend({});
SubCollection = Backbone.Collection.extend({
model: SubModel
});
Model = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'submodels',
relatedModel: 'SubModel',
collectionType: 'SubCollection'
}
]
});
When your Model fetches the JSON, it will automatically create a SubCollection under the 'submodels' property and populate it with SubModels - one for each JSON object in the array.
jsfiddle for backbone-relational: http://jsfiddle.net/4Zx5X/12/
By Hand
You can do this by hand if you want as well. In involves overriding the parse function for your Model class (forgive me if my JS is not 100% correct - been doing CoffeeScript so much lately its hardwired in my brain)
var Model = Backbone.Model.extend({
parse: function(response) {
this.submodels = new SubCollection();
// Populate your submodels with the data from the response.
// Could also use .add() if you wanted events for each one.
this.submodels.reset(response.submodels);
// now that we've handled that data, delete it
delete response.submodels;
// return the rest of the data to be handled by Backbone normally.
return response;
}
});
parse() runs before initialize() and before the attributes hash is set up, so you can't access model.attributes, and model.set() fails, so we have to set the collection as a direct property of the model, and not as a "property" that you would access with get/set.
Depending on what you want to happen on "save()" you may have to override `toJSON' to get your serialized version of the model to look like what your API expects.
jsfiddle:
http://jsfiddle.net/QEdmB/44/