I am attempting to implement a abstraction over a RESTful back-end for my persistence layer and have ran into something I find a little confusing. I am using the angular framework and more specifically the ngResource module to get access to the $resource service. I can execute my queries and work against the back-end without issue. My problem comes when integrating back into my kendo-ui datasource, the datasource never recognizes when the query has returned. From my understanding the $resource will immediately return a empty collection (array) for possible assignment and then populate that array with the results of the query when it finishes. Kendo-ui's DataSource should watch this variable and upon update reflect this back to anyone leveraging the datasource. I have successfully implemented this using a slightly different model (passing a object literal that I update myself as required) and the DataSource has no problem recognizing the updates. Any insight would be helpful!
app.provider('remotePersistence', function () {
this.$get = function ($resource) {
var definitions = {
widgets: $resource('http://127.0.0.1:3000\:3000/widget.json',{},{
archive: { method: 'POST', params: { archive: true }},
active: { method: 'POST', params: { active: true }}
})
};
var datastore = {}
var namespaces = ['widgets'];
namespaces.forEach(function (namespace) {
datastore[namespace] = {
endpoint: definitions[namespace],
cache: definitions[namespace].query()
};
});
return datastore;
};
});
app.controller(
"WidgetsSearchController",
function ($scope, remotePersistence){
$scope.widgets = undefined;
$scope.visibleWidgets = new kendo.data.DataSource({
// data: remotePersistence.widgets.cache,
transport: {
read: function (options) {
options.success(remotePersistence.widgets.cache);
}
}
});
});
//This works but is not desirable style
//$scope.widgets = remotePersistence.widgets.query(function(){ $scope.visibleWidgets.data($scope.widgets) });
The data source needs to be notified that data has been received. Perhaps the ngResource module will trigger some callback or event when it finishes loading data. Then you can use the data() method of the Kendo DataSource to populate it with data items. All Kendo UI widgets bound to that data source will receive a notification when you use the data method.
For anyone following behind here is my current implementation that works nicely. I am still a bit unhappy with the manipulation I must do for the sort to be passed but it works along with paging.
app.controller(
"WidgetSearchController",
function ($scope, remotePersistence){
$scope.visibleWidgets = new kendo.data.DataSource({
widget: {
read: function (options) {
if(options.data.sort){
options.data.order = _.map(options.data.sort, function (sortItem) {
return sortItem.field + " " + sortItem.dir
}).join(", ");
}
remotePersistence.widgets.endpoint.query(options.data, function(response){
console.log(response);
options.success(response);
});
}
},
schema: {
data: "widgets",
total: "total"
},
pageSize: 20,
serverSorting: true,
serverPaging: true
// serverFiltering: true
});
});
app.provider(
'remotePersistence',
function () {
this.$get = function ($resource) {
var definitions = {
widgets: $resource('http://127.0.0.1:3000\:3000/widgets/:id',{ id: '#id' },{
archive: { method: 'PUT', params: { archive: true }},
update: { method: 'PUT' },
active: { method: 'PUT', params: { active: true }},
query: { method: 'GET', isArray: false},
})
};
var datastore = {}
var namespaces = ['widgets'];
namespaces.forEach(function (namespace) {
datastore[namespace] = {
endpoint: definitions[namespace],
cache: definitions[namespace].query()
};
});
return datastore;
};
});
Related
So I'm currently working with $ngResouce. My api: api/Content/resource/user.post.failed returns the following:
{
"user.post.failed": "Thing.."
}
app.factory('testResource', function ($resource) {
return $resource(apiurl + '/Content/resource/:resourcename', {}, {
show: { method: 'GET', isArray: false, params: {resourcename: '#resourcename'} },
update: { method: 'PUT', params: {id: '#id'} },
delete: { method: 'DELETE', params: {id: '#id'} }
})
});
This is what I call in my controller
$scope.test = testResource.get({resourcename: 'test'});
The question is actually really simple; how do I get just the 'content part' so test. I'm now getting the whole JSON part back.
So the scope test is now: {"user.post.failed":"Thing.."}
And I want the scope to be just Thing.
Probably really simple, but I couldn't find the answer.
Use the $promise property of the object to see errors:
$scope.test = testResource.get({resourcename: 'test'});
$scope.test.$promise.catch(function onReject(response) {
console.log('ERROR: ',response.status);
console.log(response);
});
So the scope test is now: {"user.post.failed":"Thing.."} And I want the scope to be just Thing.
console.log($scope.test["use.post.failed"]);
Use the property accessor syntax.
For more info see MDN JavaScript Reference -- Property Accessors
HTML
{{ test["use.post.failed"] }}
testResource.get({resourcename: 'test'}, function(data) {
$scope.test = data['user.post.failed'];
})
I want to make a search query to my server with angularjs resource object and i have written such an resource object;
app.factory('EcriDeviceListService', function ($resource) {
var Url = "http://localhost:60766/api/EcriDeviceLists/:id/:queryText";
return $resource(Url, { id: '#Id' },{ update: { method: 'PUT' },'search': { method:'GET', {queryText:''}})
});
with this code i want to make a search query like this;
EcriDeviceListService.search({queryText:'abc'})
http://localhost:60766/api/EcriDeviceLists/queryText=abc"
how should i configure my resource object.
Thanks.
You should configure $resource object like this:
$resource('http://localhost:60766/api/EcriDeviceLists/:id/:queryText', {
id: '#Id',
queryText: ''
}, {
update: {
method: 'PUT'
},
search: {
method: 'GET'
}
});
I have a generic restful resource with angular's $resource. On any save method, I also want to set a message and boolean on whatever scope I'm in, and set a timeout for that message. So anywhere in my code where I call .save/.$save, I then attach a .finally onto it (below).
Rather than putting the same .finally onto every save I call, I'm wondering if I can just write a finally onto the actual resource itself, and have this be a generic finally for my save function.
var resource = $resource(
pageListPath,
{},
{
query: {method:'GET', isArray:true},
get: {method:'GET', url: pageDetailPath, params:{id:'#id'}, cache:true},
save: {method:'PUT', url: pageSavePath, params:{id:'#id'}},
delete: {method:'DELETE', url: pageDetailPath, params:{id:'#id'}}
}
);
return resource;
.finally(function() {
$scope.loading = false;
$timeout(function() {
$scope.message = false;
}, 2500);
});
Ideally something like
save: {
method:'PUT',
url:pageSavePath,
params:{id:'#id'},
finally:function() { doStuff() }}
is what I'm looking for. Is this possible?
I ended up writing another service to encapsulate this one, providing generic functionality for certain responses.
The API service:
pageServices.factory('PageAPI',
['$resource',
function($resource,
var resource = $resource(
pageListPath,
{},
{
query: {
method:'GET',
isArray:true
},
get: {
method:'GET',
url: pageDetailPath,
params:{ id:'#id' }
},
...,
...,
}
);
return resource;
}]
);
pageServices.factory('Page', ['PageAPI',
function(PageAPI) {
var service = {
'getPages': function() {
return PageAPI.query(function(response) {
// Do stuff with success
}, function(err) {
// Handle error
}).$promise.finally(function() {
// Generic finally handler
}
},
...,
...,
}
return service
}
])
I have few resources written on AngularJS that access a Tastypie API. Everything works fine, except for a detail: tastypie always encapsulate the actual result inside a objects attribute on a JSON, example:
/api/v1/reminder/:
{
meta: {
limit: 20,
next: null,
offset: 0,
previous: null,
total_count: 3
},
objects: [{
category: {
color: "#999999",
id: 1,
name: "Groceries",
resource_uri: "/api/v1/category/1"
},
description: "",
due_date: "2010-10-16",
id: 1,
repeat: "weekly",
resource_uri: "/api/v1/reminder/1",
value: "-50"
}, {
category: {
color: "#999999",
id: 1,
name: "Groceries",
resource_uri: "/api/v1/category/1"
},
description: "",
due_date: "2010-10-17",
id: 2,
repeat: "weekly",
resource_uri: "/api/v1/reminder/2",
value: "-50"
}
}
It was wasy to fix using a callback to the get() call:
Reminder.get().$then(function (result) {
$scope.reminders = result.data.objects;
});
But I know result.resource is an actual Reminder instance.
.factory('Reminder', ['$resource', function($resource){
var Reminder = $resource('/api/v1/reminder/:id', {}, {
get: {
method: 'GET',
isArray: false
}
});
Reminder.prototype.TESTE = function () {console.log('asd');};
return Reminder;
}])
Now I need to implement behavior on my Reminder class, and I need every element on my meta.objects to be an instance of Reminder:
Reminder.get().$then(function (result) {
$scope.reminders = result.data.objects;
result.resource.TESTE(); // -> outputs 'asd'
o = result.data.objects[0];
o.TESTE // -> undefined, obvisously
i = new Reminder(o);
i.TESTE() // -> outputs 'asd'
});
So, how to I get angularjs to understand that every object on objects is the actual result so it behaves like a list of instances?
The workaround is to creating a new list iterating on the results creating the instances, but it's not optimal...
Suggestions?
Solution by #rtcherry:
As suggested by rtcherry, I used restangular
Configuring the reading of request data:
.config(['RestangularProvider', function(RestangularProvider) {
RestangularProvider.setBaseUrl("/api/v1");
RestangularProvider.setResponseExtractor(function(response, operation, what, url) {
var newResponse;
if (operation === "getList") {
newResponse = response.objects;
newResponse.metadata = response.meta;
} else {
newResponse = response.data;
}
return newResponse;
});
}])
Loading the reminders:
function RemindersCtrl ($scope, $rootScope, Reminder) {
$scope.reminders = Reminder.getList();
}
Adding my custom method to Reminder (not as clean as ngResource, but doable):
.factory('Reminder', ['Restangular', '$filter', function(Restangular, $filter){
var Reminder = Restangular.all('reminder');
var remainingDays = function () {
//do stuff
};
// adding custom behavior
Restangular.addElementTransformer('reminder', false, function (reminder) {
reminder.remainingDays = remainingDays;
return reminder;
});
return Reminder;
}])
Solution by #moderndegree:
I used pure ngResource:
var tastypieDataTransformer = function ($http) {
return $http.defaults.transformResponse.concat([
function (data, headersGetter) {
var result = data.objects;
result.meta = data.meta;
return result;
}
])
};
...
.factory('Reminder', ['$resource', '$http', function($resource, $http){
var Reminder = $resource('/api/v1/reminder/:id', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: tastypieDataTransformer($http)
}
});
Reminder.prototype.remainingDays = function () {
// doing stuff
};
return Reminder;
}])
My controller:
Transaction.query(filter).$then(function (result) {
$scope.days = [];
var transactions = result.resource;
resource[0].remainingDays(); // it works
});
If you wanted to avoid using an additional library, you should be able to do the following:
$resource('/api/v1/reminder/', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
return data.objects;
}
])
}
});
This will append your transform to $HttpProvider's default transformer.
Note: Correct me if I'm wrong on this one but I believe this feature requires v1.1.2 or greater.
You may want to try something like restangular.
There is some configuration needed to make that work. An example is here.
I ended up doing the following in order to preserve the meta object directly on the results returned by Reminder.query() (expanding on the answer from #moderndegree).
var Reminder = $resource('/api/v1/reminder/', {}, {
query: {
method: 'GET',
isArray: true,
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
return data.objects;
}
]),
interceptor: {
response: function(response) {
response.resource.meta = response.data.meta;
}
}
}
});
That allows you to get the meta value directly on the returned object:
var reminders = Reminder.query(...);
console.log(reminders.meta); // Returns `meta` as expected.
I think it would also be possible to do something similar inside the callback from Reminder.query since the response object is available there as well.
Backbone.js's default, RESTful approach to fetching a model by the ID is easy and straight-forward. However, I can't seem to find any examples of fetching a model by a different attribute. How can I fetch a Backbone.js model by a different attribute?
var Widget = Backbone.Model.extend({
urlRoot: '/widgets',
fetchByName: function(){ ... }
});
var foowidget = new Widget({name: 'Foo'});
foowidget.fetchByName();
You can try doing something like this on your base model definition or on demand when calling fetch.
model.fetch({ data: $.param({ someParam: 12345}) });
In your case, along the lines of.
var Widget = Backbone.Model.extend({
initialize: function(options) {
this.name = options.name;
},
urlRoot: '/widgets',
fetchByName: function(){
this.fetch({ data: $.param({ name: this.name }) })
}
});
var foowidget = new Widget({name: 'Foo'});
foowidget.fetchByName();
One approach is to override Backbone.sync() method, either for all classes or for just your class. However, presumably your goal is to override fetch for just a single model. One way to do that is to directly call jQuery.ajax(...), and on success, take the response and set that, e.g.
fetchByName: function() {
var self = this;
$.ajax({
url: self.urlRoot+ "?name="+this.get('name'),
type: 'GET',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
self.set(data);
}
});
}
If the model is part of a collection you can use where() to pull out the models matching some criteria.
See http://backbonejs.org/#Collection-where
I really like the approach suggested by 'user645715'. I have adjusted the code to be more versatile. If you add this to a Backbone Model it will allow you to search the server by one or more attributes, and should work as a direct drop-in replacement for fetch.
fetchByAttributes: function(attributes, callbacks) {
var queryString = [];
for(var a in attributes){
queryString.push( encodeURIComponent(a)+'='+encodeURIComponent(attributes[a]) );
}
queryString = '?'+queryString.join('&');
var self = this;
$.ajax({
url: this.urlRoot+queryString,
type: 'GET',
dataType: "json",
success: function(data) {
self.set(data);
callbacks.success();
},
error: function(data){
callbacks.error();
}
});
}
It can be used like this:
var page = new Page();
page.fetchByAttributes({slug:slug}, {
success: function(){
console.log('fetched something');
},
error: function(){
console.log('nothing found');
}
});
this is simple model.fetch is same as $.ajax in some way
model = Backbone.Model.extend({
urlRoot: "/root/"
});
var Model = new model();
Model.fetch({
beforeSend: function () {
console.log("before");
},
data: {
param1: "param1",
param2: "param2"
},
success: function () {
console.log("success");
},
error: function () {
console.log("failure");
}
});