BreezeJS datacontext POST in angular turns into 'GET' - angularjs

I have a factory to retrieve json from my own server via POST, I'm getting the errror "TypeError: Cannot read property '$http' of undefined {query: ctor, stack: (...), message: "Cannot read property '$http' of undefined"}". Walking through breeze.debug.js I see my request is turned into a GET and thus mungs up the call. How do I force Breeze to execute my POST? Below is the datacontext based on the BreezeJS documentation on doing posts at http://www.getbreezenow.com/documentation/breezeajaxpostjs :
angular.module('rmBreezeApp.datacontext', [])
.factory('datacontext', function ($http, breeze, jsonResultsAdapter, logger, model, RMServer,AuthService) {
breeze.ajaxpost();
var postData = function (selector, argsArray) {
return {"selector": selector, "arguments": argsArray}
};
var ds = new breeze.DataService({
serviceName: RMServer,
hasServerMetadata: false,
useJsonp: false,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({dataService: ds});
model.initialize(manager.metadataStore);
return {
getShiftsForWeek: getShiftsForWeek
};
/*** implementation details ***/
function getShiftsForWeek() {
var arguments = [];
var query = breeze.EntityQuery.from("session?token=" + AuthService.authToken())
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: postData("getShiftsForWeek",[])
});
return manager.executeQuery(query).then(
function (data) {
return data;
},
function (reject) {
console.log(reject);
}
)
}
});
I've got these includes in my index.html
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/breezejs/breeze.debug.js"></script>
<script src="lib/breeze.js.labs/breeze.angular.js"></script>
<script src="lib/breeze.js.labs/breeze.ajaxpost.js"></script>

Good thing you wrote a plunker. It revealed a number of errors and opportunities which I'll summarize here.
I forked your plunker and "fixed it". It's now returning data and displaying on screen.
I tried not to change too much but I did re-organize for what seems to me a readable style. I begin by showing the complete application component set at the top:
angular.module('myApp', ['breeze.angular'])
.value('jsonResultsAdapter', createJsonResultsAdapter())
.service('datacontext', DataContext)
.service('model', Model)
.controller('sitesCtrl', SitesCtrl)
//.config(configForCORS);
Observations
CORS
Your attempt to configure the browser for CORS (configForCors) makes no difference to my browsers (IE10, Chrome, FF). I don't think it will help you. Either a browser supports CORS or it doesn't. I haven't found any way around that. You're in trouble if you need to support IE8 because it doesn't do CORS.
Configure ajaxAdapter for Breeze
The 'breeze.angular' module does that for you. Including that module as a dependency (angular.module('myApp', ['breeze.angular'])) and then injecting the 'breeze' service into the first service that needs breeze (e.g., Datacontext) is all you needed to do.
Configure Breeze for POST queries
Breeze by default sends query requests as GET requests. Your service expects POST requests. Breeze can do that.
You neglected to include the breeze.ajaxpost.js library script tag in your index.html. You need that Breeze Labs plug-in to make POST queries.
You also have to tell the Angular ajaxAdapter about it. Look in DataContext#configAjaxAdapter. You'll see:
// configure that adapter to use ajaxPost plugin
breeze.ajaxpost(ajaxAdapter);
Actually, you don't need to specify the ajax adapter; breeze will enable POST query support for whatever is the current default ajax adapter instance if you write this:
// configure that adapter to use ajaxPost plugin
breeze.ajaxpost();
But I was more explicit because I had to get the ajax adapter anyway in order to set your default headers:
// default ajax adapter is already a wrapper around current $http
// as a result of injecting the breeze service.
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
// add the app-specific default headers
// no need to set "Content-Type" for JSON; breeze does that by default
ajaxAdapter.defaultSettings = {
headers: {
"JsonStub-User-Key": "2d3b6e81-b556-4049-ab54-ec8422237c63",
"JsonStub-Project-Key": "a753777a-bbff-4db6-8755-ea8c5e60f032"
}
};
Notice I called getAdapterInstance, not initializeAdapterInstance. We want to modify the existing instance created by breeze.angular, not overwrite it with a new one.
Client metadata and jsonResultsAdapter
After making those changes, I was getting data from the service. Unfortunately, the
shape of that data didn't quite match the expectations in the jsonResultsAdapter or the metadata.
The JSON arrives in an object with a pathwaySchool property. That property returns an array. The array contains the entity data of interest; the array itself is useless and should not become a Breeze entityType.
I trained the 'jsonResultsAdapter.visitNode' method to recognize objects with a "buildingCode" property. Such objects contain the data for the PathwaySchool entities ... and we say so by returning {entityType: "PathwaySchool"} for those nodes.
Patrick's metadata describe a type named "Site". But his jsonResultsAdapter called the type "PathwaySchool". Had to call it one thing or the other. I chose "PathwaySchool".
The metadata also said we should expect an "_id" property that is the entity key. There was no such property in the data. The buildingCode property looked most like a unique identifier so I made that property the key. I also added a name property so we can display the school on screen.
Extracting entities from the ajax results
The service returns a deeply nested object. The datacontext should hide that mess from the ViewModel/Controller.
So the success callback digs in and extracts the array of "PathwaySchool"s.
return data.results[0].pathwaySchool;
We should do something if the ajax call fails. The fail callback writes the error to the console (using Angular's $log service for future testability) and then re-rejects the error.
Re-rejecting is important! We want to make sure the caller sees the error so it can process it appropriately. If we did nothing, the Angular would assume we had fixed the error and pass undefined to the caller's success callback. That's not what we want. We want to pass the error along to the caller's fail callback. So we re-reject the error and return that (failed) promise.
ViewModel/Controller
Patrick's controller called the datacontext and simply assigned the results of the call to the $scope.sites array, expecting angular to bind to those results:
$scope.sites = datacontext.getSites(); // Not good!
That won't work. We don't want Angular to display the promise. We want to set the $scope.sites array after the promise resolves with the appropriate entities.
We should also be prepared to display an error if our call for data fails.
Here's how I get the sites into $scope:
$scope.sites = [];
datacontext.getSites()
.then(function(sites){
$scope.error = ''; // clear previous error if any
$scope.sites = sites;
})
.catch(function(error){
$scope.error = "'getSites' failed: " + error.message +
". Check console log for details.";
});
The View can bind to $scope.error if there's a problem.
View Binding
Finally, we display the results or error on screen. I need a small amount of HTML for the purpose:
<div ng-controller="sitesCtrl">
<div ng-repeat="site in sites">#{{site.buildingCode}} {{site.name}}</div>
<p class="errorMessage" ng-if="error">{{error}}</p>
</div>

I added a passing test that looks like yours to the ngDocCode sample:
var resource = '/breeze/Northwind/CustomersWithFilterOptions?token=1234ABCD';
beforeEach(function () {
breeze.ajaxpost(); // add POST option to ajax adapter
em = newEm(); // fresh EntityManager before each test
});
...
it("by CompanyName w/ appended URL query string", function (done) {
// See SO question
// http://stackoverflow.com/questions/27364078/breezejs-datacontext-post-in-angular-turns-into-get
var companyName = 'Die Wandernde Kuh';
// Notice the query string appended to the resource
EntityQuery.from(resource + '?token=1234ABCD')
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: {
CompanyName: companyName
}
})
.using(em).execute()
.then(success)
.then(done, done);
function success(data){
expect(data.results).to.have.length(1);
var cust = data.results[0];
expect(cust.CompanyName).to.equal(companyName);
}
});
The request over the network looks like this:
POST /breeze/Northwind/CustomersWithFilterOptions?token=1234ABCD HTTP/1.1
Host: localhost:58066
Connection: keep-alive
Content-Length: 35
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
The request payload is
{"CompanyName":"Die Wandernde Kuh"}
I'm not sure what's different between ours and yours. But the ball is in your court to provide a repro (plunker or jsFiddle).
I didn't see anything glaringly obvious in your question's code. I don't know why you're injecting $http but that shouldn't hurt.
I don't see where you're taking the dependency on the breeze labs 'breeze.angular' module but you must be doing it somewhere or Angular would have failed ("can't find breezeProvider") when it injected "breeze" into your "datacontext" factory.

Related

getting Backbone collection size?

I've a Backbone Collection
var users = Backbone.Collection.extend({
url: 'https://localhost:2222/users',
model: User
});
In Controller I need to get no of users.
var usersList = new Users();
usersList .fetch({
beforeSend: CommonUtils.sendAuthentication,
data: $.param({ group: group.id})
});
I tried usersList.length it returns 0 but console shows some data in 'usersList'
I logged
console.log(usersList);
console.log(usersList.length);
in 'child' object length shows as 0 but inside that length is 4.
Are you sure you are counting the Length after the fetch has come in? Remember that fetching takes some time and your javascript will not wait for it.
By default Backbone .fetch() makes asynchronous HTTP (Ajax) request which does not finish right away, that is why you have to use success and error callback functions which will be fired accordingly after .fetch() returns a response from the server.
Both success and error receives fetched collection, server response and option objects.
Simple example:
usersList.fetch({
success: function(collection, response, options){
console.log(collection.length);
// Method which would use your fetched colletion
myFancyMethodUsingCollection(collection);
},
error: function(collection, response, options){
}
});
To expand, Backbone collection.fetch([options]) is based on Backbone.sync(method, model, [options]) which is based on jQuery.ajax which by default performs asynchronous requests.
Behavior of .fetch() can be modified by passing options to the method.
Options may contain settings for Backbone (for ex.: silent fetch which does not fire collection changed event and so on.) or jQuery.ajax settings.
Note that using async: false option is discouraged by jQuery.
Synchronous requests may temporarily lock the browser, disabling any
actions while the request is active. As of jQuery 1.8, the use of
async: false with jqXHR ($.Deferred) is deprecated; you must use the
success/error/complete callback options instead.
Backbone.Collection.fetch()
Backbone.sync()
jQuery.ajax()

Angular REST returning Syntax error on JSON parse

I am getting an error "syntax error JSON parse unexpected end of data at line 1 column 1 of json data". The RESTful service is runnning, a straight test returns valid json data (verified at http://jsonlint.com/)
[{"id":2,"name":"Flourescent Penetrant Inspection","description":"The fluorescent penetrant inspection process.","abbreviation":"FPI","tempId":null,"processType":"INSPECTION","indicationTypes":[{"id":1,"name":"Crack","description":"An identified crack on the piece.","abbreviation":"","tempId":null,"groupName":"","markupType":"LINE","useSizeDescription":true,"sizeDescription":"<= 1 in.::> 1 in.","rejectReason":"Crack","defaultDisposition":"MRB"},{"id":2,"name":"Inclusion","description":"An identified inclusion on the piece.","abbreviation":"","tempId":null,"groupName":"","markupType":"DOT","useSizeDescription":false,"sizeDescription":"","rejectReason":"Inclusion","defaultDisposition":"REWORK"}]},{"id":4,"name":"CMM","description":"The CMM process.","abbreviation":"CMM","tempId":null,"processType":"INSPECTION","indicationTypes":[]}]
The error in the HTTP response, yet it is returning a 200 message. The angular app is seeing it as an empty array. Any ideas?
The applicable part of the Controller is:
indicationTypeController.constant("indicationTypeUrl", "http://localhost:8080/services/api/indication-types.json");
indicationTypeController.controller('indicationTypeController', function ($scope, $http, $resource, indicationTypeUrl) {
$scope.indicationTypeResource = $resource(indicationTypeUrl+":id", { id: "#id" },
{ 'create': {method: "POST"}, 'update': {method: "PUT"}
});
$scope.listIndicationTypes = function () {
$http.get(indicationTypeUrl)
.success(function(data){
$scope.indicationTypes = data;
});
//$scope.indicationTypes = $scope.indicationTypeResource.query();
}
. . . .
As you can see I am currently using a $http.get, I also tried a query().
Any
Usually, the $http promise returns an object that contains the headers and the data. In your success handler for the $http, you have
$http.get(indicationTypeUrl)
.success(function(data){
$scope.indicationTypes = data;
});
I'm pretty sure that data is the full response and you need to get the specific data by using the data property of this object. Therefore, this would become the following:
$http.get(indicationTypeUrl)
.success(function(data){
$scope.indicationTypes = data.data;
});
In other implementations, instead of the passed in parameter being called data, it's usually called result, so that you can reference the contained data like result.data instead of data.data
The other thing to make sure of is that the Content-Type is set appropriately between the server and client. If it's not application\json you'll probably run into issues.
This is an CORS issue, please add the following to the response header, before sending the result.
"Access-Control-Allow-Origin" : "*"
For instance, if you are using play server (Java code) to serve the request, the following statement should be added to the method where you are returning the data from
response().setHeader("Access-Control-Allow-Origin", "*");

angularjs custom REST action and error handling

I'm having some trouble with error handling in a little angularjs application. I'm interacting with a Flask backend and a Postgres DB.
I have a factory service
appointServices.factory('Appointments', ['$resource', function($resource){
return $resource(someUrl, {}, {
query: { ... }
,
create: {
method: 'POST'
,url: 'http://somedomain:port/new/:name/:start/:end/:treatment'
,params: { start: '#start', end: '#end', name: '#name', treatment: '#treatment' }
,isArray:false
}
});
}
]);
Inside a controller I'm making the following call
Appointments.create($scope.appointment, function(value, responseHeaders) {
// success handler
console.debug('success: ', JSON.stringify(value));
}, function(httpResponse) {
// error handler
console.debug('error: ', JSON.stringify(httpResponse));
});
Here $scope.appointment contains the relevant parameters for the create action.
Now, in the backend I'm able to catch DB errors involving constraints and I'm trying to return an error code with a 'meaningful' message. So I have a python method
def create(name, start, end, treatment):
try:
...
transaction_status = 'ok'
code = 200
except IntegrityError as e:
...
transaction_status = 'IntegrityError'
code = 500
finally:
...
return make_response(transaction_status, code)
Everything works fine, I'm able to talk to the backend, create new data and insert this in the DB. As I said, any violation of the constraints is detected and the backend responds
curl -X POST "http://somedomain:port/new/foo/bar/baz/qux" -v
...
< HTTP/1.0 500 INTERNAL SERVER ERROR
...
IntegrityError
So, the problem is, no matter whether the action create was successful or not, the intended error handler specified inside the controller is always fired. Moreover, I always end up with a status code 404 in the httpResponse. Firebug shows correctly the code 500 as above, though.
Anybody has any idea of why I'm getting this behavior?
Any suggestions on how to improve the error handling mechanism are also welcome.
Thx in advance.
P.S. Following the documentation on $resource I have also tried variations on the factory service call, e.g.
Appointments.create({}, $scope.appointment, successCallback, errorCallback);
Appointments.create($scope.appointment, {}, successCallback, errorCallback);
with the same results.
Update:
Forgot to mention the important fact that I'm interacting with the backend via CORS requests. The POST request in create above is having place with the OPTIONS method instead. As I mentioned everything is working correctly except for the error response.
Under further investigation, I tried to isolate the factory service, in case I did something wrong, and I also tried the approach shown in the credit card example ($resource docs), but with no positive result.
However, I came up with two workarounds. Firstly, I was able to create a plain JQuery POST request, as in the example shown in the docs. This time, the request is not replaced by OPTIONS and I got the error code correctly.
I also managed to connect to the backend with the low-level $http service as follows:
var urlBase = 'http://somedomain:port/new/:name/:start/:end/:treatment';
var url = urlBase.replace(/:name/g, $scope.appointment.name);
url = url.replace(/:start/g, $scope.appointment.start);
url = url.replace(/:end/g, $scope.appointment.end);
url = url.replace(/:treatment/g, $scope.appointment.treatment);
// force method to be POST
var futureResponse = $http({ method: 'POST', url: url });
futureResponse.success(function (data, status, headers, config) {
console.debug('success: ', JSON.stringify(data));
});
futureResponse.error(function (data, status, headers, config) {
console.group('Error');
console.debug(JSON.stringify(status));
console.debug(JSON.stringify(data));
console.groupEnd();
});
This time, as in the case of JQuery, the request is done effectively with POST and error codes are correctly received.
Notice also that I'm not calling $http.post but I set the method to POST as part of the object parameter to $http, otherwise the connection takes places with OPTIONS as before.
Still trying to figure out what is happening with $resource.

angularjs $http.jsonp goes to .error instead of .success even when 200 is received

I'm building an AngularJS app which calls a NodeJS server that gets data from a DB.
The NodeJS returns a JSON.stringify(someArrayWithData).
Here is the AngularJS code:
$scope.getMainGroups = function(){
$http.jsonp("http://127.0.0.1:3000/get/MainGroups?callback=JSON_CALLBACK").success(function(data, status, headers, config) {
$scope.MainGroups = data;
}).error(function(data, status, headers, config) {
$scope.MainGroups = status;
});
};
$scope.MainGroups is going to the .error instead to the success even when I can see in the chrome debugger that the result came back (200 ok).
What am I missing?
You must return a valid JSON response on the server side. The 200 OK is just the GET request that is injected into the DOM. I bet you are not returning a valid JSON response from the server with right response code.
Here is an example on how to construct the response on the server side (PHP):
Simple jQuery, PHP and JSONP example?
As answered here you need to change the NodeJS response. The JSONP answer needs to start with the exact text that AngularJS added in the URL (for example 'angular.callbacks._1').
// changes in your server script used by NodeJS
// Required for JSONP calls, 'callback' matches the '?callback=JSON_CALLBACK'
app.set('jsonp callback name', 'callback');
// ... in your response change json to jsonp
res.jsonp(votes);
After doing these changes, when the server detects a 'callback' in the parameters, answers like this:
/**/ typeof angular.callbacks._1 === 'function' &&
angular.callbacks._1([{"votes":3,"title":"new topic", etc...
Now yes, AngularJS correctly calls back the 'success' function.

Angular $httpProvider transformResponse data contains local HTML DOM elements?

When I instantiate the following code in an AngularJS app, I get weird data in the transformResponse function (bottom of code). I'm not calling the $resource function from any controller, just loading the script in a browser. The data variable (see code) contains the HTML of the current partial, when loading the app.
This seems odd. Is this the way it's supposed to be?
var buddyServices = angular
.module('buddyServices', ['ng','ngResource'])
.factory('Buddy',
function ($resource) { console.log('resource');
return $resource('http://webservice.buddyplatform.com/v1/:service',
{service:'', BuddyApplicationName: 'xxx',
BuddyApplicationPassword: 'yyy'}
);
}
)
.config(function($httpProvider){
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.transformResponse = function(data) {
console.log(data);
return 'TEST: '+data;
};
});
=== EDIT ===
It just daunted on me: $httpProvider handles all http requests, so a page load is one of those. I'm guessing a bit now, but it seems probable. So, the question then becomes: Is there an "easy" way to constrain the data in the code above to only those requests performed by my service?
transformResponse takes another parameter headersGetter. You can use this to get the headers send with the response. Look for Content-Type header header. It should contain application/json

Resources