Due to some extraordinary circumstances I am currently busy with creating a client-only, server-less app using AngularJS. Its single purpose (for the moment) is to read a .json file, containing an array of Activity objects as follows:
[{
"id": "sta",
"title": "IT Strategy & Governance",
"type": "management",
"order": 1
},
{
"id": "por",
"title": "Portfolio Management",
"type": "management",
"order": 2
},
{
"id": "org",
"title": "Organization & Process Management",
"type": "management",
"order": 3
}]
Please keep in mind that this file is static and not generated by any kind of backend. I would now like to achieve to objectives.
Objective 1 is to load this complete list of objects. I accomplish this using an Angular $resource as follows, and it works beautifully:
Service:
pexServices.factory('Activities', function($resource) {
return $resource('data/activities.json', {});
});
Controller:
pexControllers.controller('ActivityCtrl',
function($scope, Activities) {
$scope.activities = Activities.query();
}
);
Objective 2 is to get one individual record out of this array, selecting it via its ID. As before, the data shall be pulled out of the one, big data file including all data objects.
My idea is too add a custom get function to the $resource already introduced above and include some transformResponse code that extracts the requested element:
pexServices.factory('Activities', function($resource) {
return $resource('data/activities.json', {}, {
get: {
method: "GET",
transformResponse: function(rawData, headersGetter) {
var jsonData = jQuery.parseJSON(rawData);
var requestedObject = jsonData.filter(function(activity) {
return activity.id == "THEREQUESTEDID";
});
return requestedObject.shift();
}
}
});
});
This works great, but only as long as I hard code the requested ID at THEREQUESTEDID. However, I obviously want to pass a variable parameter into the get function in order to pull the needed object, like this:
$scope.activity = Activities.get({activityId: "sta"});
Unfortunately, this is the point where I have been stuck for a while now. How do I enable a parameter to pass all the way from my controller to the transformResponse function, where I can use it to filter the array?
As said before, the file itself is static and won't react to any parameters, so I need to do the filtering manually on the client.
Thanks a lot for your support and for keeping in mind my limited JS skills. :-)
(In case you have another entirely different solution to my problem in mind, please feel free to share it as well! This is just my first shot.)
Not this way! You need to create a service with methods and wrap your resource object inside it. The service would be something like this
pexServices.factory('Activities', function ($resource) {
var service = {};
var cachedActivities = [];
service.getAll = function () {
cachedActivities = $resource('data/activities.json', {});
return cacheActivities;
}
service.getActivity = function (id) {
var requestedObject = jsonData.filter(function (activity) {
return cachedActivities.id == id;
});
return requestedObject.shift();
}
return service;
});
Now your service has 2 methods, one for list and one for specific activity. Also the activities retrieved from server are cached in a local variable. The only issue here is that getActivity will only work if you call getAll first. If you want to fix it, move to a promise based approach.
Related
Here's an example that uses Backbone with React.
He defines a Model: var _todos = new Backbone.Model();
And then adds two functions to it:
var TodoStore = _.extend(_todos, {
areAllComplete: function() {
return _.every(_todos.keys(), function(id){
return _todos.get(id).complete;
});
},
getAll: function() {
return _todos.toJSON();
}
});
What I don't understand is why areAllComplete is being applied to a Model instead of to a Collection.
Shouldn't this be a function in a Collection that will get all of its models and check that complete attribute.
Similarly, I would expect getAll to belong to a Collection - get all of its models.
This example seems to replace Collection with Model.
Maybe I don't totally understand how models are used.
That example is using Backbone.Model in a fairly wierd way in my opinion.
This is where it's adding new todos to the store:
var id = Date.now();
_todos.set(id, {
id: id,
complete: false,
text: text
});
}
What it's basically doing is setting every todo-item as an attribute of the Model, using the id as the attribute name. It ends up with _todos.attributes looking something like below
{
"1436600629317": {
"id": 1436600629317,
"complete": false,
"text": "foo"
},
"1436600629706": {
"id": 1436600629706,
"complete": false,
"text": "bar"
}
}
That's the same output you get from _todos.toJSON(). I've no idea why they decided to implement it like that, if they were to try using Backbone.Sync they'd end up with a server API that's not exactly RESTful. It seems strange to use Backbone without leveraging any of the things Backbone provides. There's a reference to the change event here but I don't see it being used anywhere. You could easily reimplement that store using any regular JS object.
The only thing that example seem to be actually using from Backbone is Backbone.Events in the dispatcher. You're totally right that using a Collection would make way more sense because then you could actually make it talk to a REST based server API. That example seems to only use Backbone for the sake of using Backbone.
This is code from an Angular introduction video series, which explains how to populate angular controllers with data from persisted memory, but stops just short of explaining how to add the new product reviews to the persisted memory.
There do seem to be some articles explaining how to do this, but since I am very new to angular, I'm afraid I couldn't understand any of them.
I have figured out the syntax for making post requests using $http, but I don't see how to fit that code into the existing structure, so that it will 1) be called when pushing a new element to the reviews array, and 2) update the view when completed.
I am interested to learn a basic way to add the new product review to persistent memory.
(function() {
var app = angular.module('gemStore', ['store-directives']);
app.controller('StoreController', ['$http', function($http){
var store = this;
store.products = [];
$http.get('/store-products.json').success(function(data){
store.products = data;
});
}]);
app.controller('ReviewController', function() {
this.review = {};
this.addReview = function(product) {
product.reviews.push(this.review);
this.review = {};
};
});
})();
The JSON looks like this:
[
{
"name": "Azurite",
"description": "...",
...
"reviews": []
},
...
]
If the store-products.json is just a file on the server, you'll need an actual backend implementation (in PHP, nodejs, etc.) to actually update the file (or more typically just return the content from the database).
Normally you would make a save method and not post on every modification, though. But, in either case, depending on your backend, usually the implementation is as simple as $http.put('/store-products', store.products) whenever you click a "save" button. Typically, the put can return the same data, so there's typically no need to update the view since you just set it exactly to your state. But, if you have possibility of concurrent editing, and the put returns the modified data, it would look like your get:
$http.put('/store-products', store.products).success(function(data){
store.products = data;
});
For adding an item, it might almost identical, depending on your data model:
$http.post('/store-products', newProduct).success(function(data){
store.products = data;
});
In this case the POST gives an item to add and returns all of the products. If there are a lot of products -- that is, products are more like a large database than a small set in a "document", then the post would more typically return the added item after any server processing:
$http.post('/store-products', newProduct).success(function(newProductFromServer){
store.products.push(newProductFromServer); //if newProduct wasn't already in the array
//or, store.products[newProductIdx] = newProductFromServer
});
If you really wanted to call this function on every modification instead of a save button, you can use a watch:
$scope.$watchCollection(
function() { return store.products; },
function() { /* call the $http.put or post here */ }
}
I have an API that returns one resource at a time.
As an example, say I have two related resources:
Topics
Posts
Topics hasMany Posts, and Posts belongsTo a Topic.
According to the documentation (and everything I've tried and have been reading about), Ember-Data expects the JSON out of the API to be similar to the following, where both the data for Topic and the data for Posts are provided simultaneously.
"topic": {
"id": ...,
"title": ...,
"author": ...
},
"posts": [{
"id": ...,
"commenter": ...,
"text": ...
}, {
"id": ...,
"commenter": ...,
"text": ...
}]
As I mentioned at the start of the post, my API does not do this. It would return the Topic data in one call, and the Posts data for that Topic in a second call. Changing my API to work exactly how Ember wants it is not an option.
My question is, how can I extend/override the RESTAdapter and RESTSerializer in Ember-Data to support loading data from multiple API calls? Is this even possible?
I tried following this example, but it looks like the data here is already loaded (it tries to sideload data that is already known to the system): http://mozmonkey.com/2013/12/loading-json-with-embedded-records-into-ember-data-1-0-0-beta/
I also tried to override the extractFindAll function of the ApplicationSerializer, but can't figure out how to get the rest (no pun intended) of the application to wait for all of the relationships to load. I iterate through all of the relationships on the model, and load their data, but I don't see a way to shuttle that data back into the original payload:
extractFindAll: function (store, type, payload, id, requestType) {
var promises = [];
type.eachRelationship(function (key, relationship) {
promise.push(Ember.RSVP.Promise(function (resolve, reject) {
this.store.find(key, payloadEntry[key]);
}));
});
// can't figure out how to get the relationship payloads
// back to the original payload
Ember.RSVP.addSettled(promises).then(function () {
return this._super(store, type, payload, id, requestType);
});
}
Any help is appreciated.
That exactly describes using the async property.
Models
App.Foo = DS.Model.extend({
bars: DS.hasMany('bar', {async: true});
})
App.Bar = DS.Model.extend({
baz: DS.attr()
})
Adapters
App.FooAdapter = DS.RESTAdapter.extend({
//anything custom
});
App.BarAdapter = DS.RESTAdapter.extend({
//anything custom
});
If you don't do anything custom, you should just define App.ApplicationAdapter = DS.RESTAdapter; to be a site wide adapter.
Serializer
App.FooSerializer = DS.RESTSerializer.extend({
//anything custom
});
App.BarSerializer = DS.RESTSerializer.extend({
//anything custom
});
If you don't do anything custom, you don't need to define the rest serializers, they will use their default serializer associated with whichever adapter they're using.
Example
Important to note, I was lazy and my mock response for bars always returns the same thing regardless of any call to /bar*
http://emberjs.jsbin.com/OxIDiVU/481/edit
I'm loading two sets of data separately but I'd like them to be related. Allow me to explain.
Firstly, I'm not using Ember-data but am instead using a simple $.ajax wrapper as outlined in this post from one of the Discourse team.
I have a concept of channels and programmes.
Channels JSON:
[{
"ID":94,
"Name":"BBC1"
},
{
"ID":105,
"Name":"BBC2"
}]
I have to gather the IDs from this JSON to be able to then request the programmes for those channels. So a response from the programmes endpoint will look a bit like this:
Programmes JSON:
{
"Channels": [
{
"Listings": [
{
"Id": "wcy2g",
"Genres": "Education",
"S": "2013-04-26T10:45",
"E": "2013-04-26T11:15",
"T": "Crime Scene Rescue"
}
]
},
{
"Listings": [
{
"Id": "wcwbs",
"Genres": "Current affairs,News",
"S": "2013-04-26T11:00",
"E": "2013-04-26T12:00",
"PID": "nyg",
"T": "Daily Politics"
}
]
}
]
}
Each Listings array can contain x amount of programmes and the objects in the Channels array relate to the order in which they are requested (by the IDs from the Channels.json) so in this case Channels[0] is BBC1 and Channels[1] is BBC2.
What I'd like is to request these two data sets as a single JSON request each but then somehow relate them. So having a channel controller that has x amount of programme models. I also need to render the channels and their programmes in two different templates
Was thinking I could iterate through the channels.json and use the index of the item to look up the relevant items in programmes.json and create the relationship that way.
Not too sure how to use Ember to achieve this though.
Thanks
I did something very similar to this and got it working in ember. I'll sketch out what I did, using your objects. Note that I'm fairly new to ember so a grain of salt may be necessary.
First, you'll want to have model objects for "Channels", "Channel" and "Programme". This will eventually let you have Controllers and Routers for each of those things, matching up nicely with ember's naming conventions. The ChannelsData will have many ChannelData objects in it, and each ChannelData will have many ProgrammeData objects. How do you get these populated?
In your ChannelsRoute class you can have a model() function which returns the model data for that route. Your model function can call create() on ChannelsData to create an instance, and then call a loadAll function on ChannelsData. ChannelsData implements loadAll() using your preferred flavor of ajax. The dead-simple easiest thing to do is to have that function do both of your ajax calls and build the entire tree of data.
You will then find that you'll run into trouble if your ChannelRoute class tries to call its model(), for instance if you enter a path like #/channels/105 directly into the browser. To work around that, make a simple object store of your own on your App object, something like App.ChannelsStore = {}, and when you create each Channel put a reference to it in your ChannelsStore (by id). Then your ChannelRoute.model function can look up its model from that store. But only if ChannelsRoute.model has completed first!
If the user entered that #/channels/105 route as the very first entry into your app, then your code will go through the ChannelsRoute.model() method, and immediately go through the ChannelRoute.model() method, probably before your ajax has completed. In that case you can have the ChannelRoute.model() method create a temporary Channel (with no programmes, for instance) and put that in the App.ChannelsStore. Your logic for building up the whole tree of data should then be willing to check the ChannelsStore to see if an object with a given id already exists, and if so to update it. You end up with something like:
App.ChannelRoute = Ember.Route.extend({
model: function(params) {
var channel = App.ChannelsStore[params.channel_id];
// create stub version if not found
if (!channel) {
channel = App.ChannelData.create({ID: params.channel_id});
App.ChannelsStore[params.channel_id] = channel;
}
return channel;
}
});
(You may end up building a ProgrammeStore similarly, but that's just more of the same.)
The updating of the temporary object actually demonstrates a very cool aspect of ember, which is that your ui may be presented with the values from the temporary object, but then when your ajax call completes and the Channels and Programmes are all loaded - your ui will update properly. Just make sure you update your objects with the set() method, and that your ui templates are happy to work with partial data.
I have an existing service written with the .NET Web API.
As an example, this service returns JSON in the following format:
[
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
]
However, as per the Ember.js Rest Adapter documentation, Ember would expect JSON in the following format:
{
"persons": [
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
]
}
Because of this, Ember is returning the following error:
Your server returned a hash with the key 0 but you have no mapping for it
By no means do I plan on changing my service API and how it returns data.
Would it be possible to get Ember.js (latest version) to work with the existing data my service is returning? And, if so, how can I implement that?
Ember is very flexible in that sense, giving the ability to extend the adapter and serializer in order to integrate your app with any backend API.
You should get the WebAPIAdapter, which is part of the Ember.js Template for Web API.
Additionally, you might wanna take a look into this project I wrote as an example, based on that same template (with some modifications I did on my own). It's still under development and it doesn't feature all the best practices (yet), but I'd say it's a valid example.
You should also take a look into this repo / library (You can install it via NuGet too), which allows you to pre-compile your Handlebars templates directly into Ember.TEMPLATES collection.
in the web api just return new { object }
var persons = _personbService.GetPeople;
return new { persons };
this will wrap your object in an object of the same name. If you need to call the object something else just do
new { SomeOtherName = persons; }
I was having this issue in ember and found the best solution for me was to build a new serializer and override the normalizePayload method. code below:
export default DS.RESTSerializer.extend({
normalizePayload: function(payload) {
var root = "posts";
var output = {};
output[root] = payload;
return output;
}
});
This wraps the initial response and adds the root to it, hope it helps!