Originally in my app, I created controllers with very basic $http calls to get a resource by getting the ID of an object from the url ($routeParams). Ng-repeat display the results correctly.
However, I noticed refreshing in a later view (different controller) wiped out the data and broke the page. So, I created a function on the service to be used in multiple controllers, to check whether the data has is available and to react as follows:
1) If the resource is defined, return it (no API call)
2) If the resource is not defined, get the id from the url and get it from the API
3) If the resource is not defined & you can't get the ID, just return false.
However, this broke the code: the template rendered before the service returned the data, and ng-repeat did not update. The code looks like this:
angular.module('myApp', ['ngCookies'])
.config(...)
.service('myService', ['$cookies', '$http', function($cookies, $http) {
myData = {};
return {
getData:function(dataID) {
if(myData.name) {return myData);
else if (dataID && dataID !== '') {
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
return myData;
}
}
else { return false; }
}
}
}]);
function myCtrl($scope, $http, $routeParams, myService) {
$scope.data = myService.getData($routeParams.dataID);
...
}
And here's the template. It's in jade, which means rather than angle brackets, you just list the element with parameters in parenthesis right after, and content after the parenthesis.
h2 My heading
ul
li(ng-repeat='option in data')
a(href="#", ng-click='someFuncInCtrl(option.name)') {{ option.name }}
When the controller did the $http.get itself, the ng-repeat worked fine because the $scope was updated in the ".success" callback. Now that there's a service that returns the data after a slight delay, "$scope.data" is just undefined, the ng-repeat list is empty.
I used a console.log to check myData right before return "return myData", and the myData is working, it just isn't returned in time, and for whatever reason the list is not updating whenever $scope does get the data.
I looked a using $routeProvider's resolve... but that makes getting the ID from the url challenging, as the resolve object doesn't seem to have access to $routeParams. I know that $scope.$apply is supposed to help update the scope when it's altered by outside functions... but I have no clue where to put it. The most similar problem on SO didn't use a service.
I tried:
$scope.$apply($scope.data = myService.getData($routeParams.dataID));
And
$scope.$apply(function() {
$scope.data = myService($routeParams.dataID);
});
Both times I only got Error: $digest already in progress.
The problem is on the way you interact with the service. Since your getData function can return both synchronous and/or asynchronous information, you can't just use normal return(s).
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
return myData;
});
The return on the above snippet will not return anything from getData because it will be executed on the context of the $http.get success callback (and not on the getData call stack).
The best approach for handling sync and async service requests is to use promises.
Your getData function should look something like this:
getData:function(dataID) {
var deferred = $q.defer();
if(myData.name) {
deferred.resolve(myData);
} else if (dataID && dataID !== '') {
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
deferred.resolve(myData);
// update angular's scopes
$rootScope.$$phase || $rootScope.$apply();
});
} else {
deferred.reject();
}
return deferred.promise;
}
Note: You need to inject the $rootScope on your service.
And on your controller:
function myCtrl($scope, $http, $routeParams, myService) {
myService.getData($routeParams.dataID).then(function(data) {
// request was successful
$scope.data = data;
}, function() {
// request failed (same as your 'return false')
$scope.data = undefined;
});
}
Related
In our Android project (download manager) we need to show built-in web browser so we able to catch downloads there with the all data (headers, cookies, post data) so we can handle them properly.
Unfortunately, WebView control we use does not provide any way to access POST data of the requests it makes.
So we use a hacky way to get this data. We inject this javascript code in the each html code the browser loads:
<script language="JavaScript">
HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = formSubmitMonitor;
window.addEventListener('submit', function(e) {
formSubmitMonitor(e);
}, true);
function formSubmitMonitor(e) {
var frm = e ? e.target : this;
formSubmitMonitor_onsubmit(frm);
frm._submit();
}
function formSubmitMonitor_onsubmit(f) {
var data = "";
for (i = 0; i < f.elements.length; i++) {
var name = f.elements[i].name;
var value = f.elements[i].value;
//var type = f.elements[i].type;
if (name)
{
if (data !== "")
data += '&';
data += encodeURIComponent(name) + '=' + encodeURIComponent(value);
}
}
postDataMonitor.onBeforeSendPostData(
f.attributes['method'] === undefined ? null : f.attributes['method'].nodeValue,
new URL(f.action, document.baseURI).href,
data,
f.attributes['enctype'] === undefined ? null : f.attributes['enctype'].nodeValue);
}
XMLHttpRequest.prototype.origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
// these will be the key to retrieve the payload
this.recordedMethod = method;
this.recordedUrl = url;
this.origOpen(method, url, async, user, password);
};
XMLHttpRequest.prototype.origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
if (body)
{
postDataMonitor.onBeforeSendPostData(
this.recordedMethod,
this.recordedUrl,
body,
null);
}
this.origSend(body);
};
const origFetch = window.fetch;
window.fetch = function()
{
postDataMonitor.onBeforeSendPostData(
"POST",
"test",
"TEST",
null);
return origFetch.apply(this, arguments);
}
</script>
Generally, it works fine.
But in Google Mail web interface, it's not working for some unknown reason. E.g. when the user enters his login name and presses Next. I thought it's using Fetch API, so I've added interception for it too. But this did not help. Please note, that we do not need to intercept the user credentials, but we need to be able to intercept all, or nothing. Unfortunately, this is the way the whole system works there...
Addition #1.
I've found another way: don't override shouldInterceptRequest, but override onPageStarted instead and call evaluateJavascript there. That way it works even on Google Mail web site! But why the first method is not working then? We break HTML code somehow?
I have a model called Instance which works fine.
define([], function(){
return Backbone.Model.extend({
urlRoot:'/api/Instances',
parse:function(content){
return content.data;
}
});
});
My REST at here
http://localhost:3000/api/Instances/1
returns this
{"status":"success","data":{"id":1,"name":"bangladesh","write":null,"read":null,"createdAt":"2015-09-01T23:03:16.000Z","updatedAt":"2015-09-01T23:03:16.000Z","UserId":1}}
hence the parse function in my model. All good so far.
If I just call
http://localhost:3000/api/Instances
Then I get a block of all my records,
{"status":"success","count":212,"data":[
{"id":1,"name":"bangladesh","write":null,"read":null,"createdAt":"2015-09-01T23:03:16.000Z","updatedAt":"2015-09-01T23:03:16.000Z","UserId":1},
{"id":2,"name":"abqride","write":null,"read":null,"createdAt":"2015-09-01T23:03:58.000Z","updatedAt":"2015-09-01T23:03:58.000Z","UserId":1},
....
And my collection code is just this
define(['models/instance.js'], function(Model){
return Backbone.Collection.extend({
url:'/api/Instances',
model:Model,
parse:function(content){
return content.data;
}
})
});
The above code for the Collection will create 212 models. I've checked that with an initialise function in the model just to see if it was being called OK with the right data, and it is.
But in my view code when I go
this.collection.each(function(model) {
console.log("model id="+model.get("id")+" count="+count++);
out+=model.get("id")+"="+model.get("name")+"<br>";
});
there's nothing in these models, but there are 212 of them, I just get "undefined=null" 212 times.
I can see three options, two of which involve customizing the use of Backbone: 1. Calling collection.sync manually and then executing custom code afterward, 2. Passing a custom option in the initial collection.fetch() and looking for it in the Model parse() method.
These two options don't solve the problem at the source, however, since the concern is at the initial response level. In order to apply a response-level filter to your data, try overriding Backbone.ajax() before starting your application:
Backbone.ajax = function () {
var settings = arguments[1] || arguments[0]; // jQuery.ajax(url[, settings])
var success = settings.success;
settings.success = function (data, status, xhr) {
if (success) { success(data.data, status, xhr); }
};
return Backbone.$.ajax.apply(Backbone.$, arguments);
};
I've got a mySql db with non-standard IDs and field names, so I was trying to use both jsonResultsAdapterProvider and setRestangularFields. Here's the code in my app.config file:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
return jsonResultsAdapterProvider.$get().camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonResultsAdapterProvider.$get().decamelizeKeys(elem);
});
It's all good until I try to do a put/save. When I look at the request payload within the browser dev tools, it's: {"undefined":12842} (but the url is correct, so I know the id is set) If I don't use the ResultsAdapter and change the id field to Person_ID, payload looks good, so I know I'm making the right calls to Get and Save the Restangular objects. But for what it's worth, here's the code:
$scope.tests = Restangular.all('members').getList().$object;
vm.testEdit = function () {
$scope.test = Restangular.one('members', 12842).get().then(function(test) {
var copy = Restangular.copy(test);
copy.title = 'xxxx';
copy.put(); // payload was: undefined: 12842
});
}
// I also tried customPUT...
// copy.customPUT(copy, '', {}, {'Content-Type':'application/x-www-form-urlencoded'});
I tried "fixing" the id other ways too, too. like this:
Restangular.extendModel('members', function(model) {
model.id = model.personID;
return model;
});
but that messed up the urls, causing missing ids. And I tried getIdFromElem, but it only got called for my objects created with Restangular.one(), not with Restangular.all()
Restangular.configuration.getIdFromElem = function(elem) {
console.log('custom getIdFromElem called');
if (elem.route === 'members') { // this was never true
return elem[personID];
}
};
It seems like Restangular needs to substitute 'personID' most of the time, but maybe it needs 'Person_ID' at some point during the Save? Any ideas on what I could try to get the Save working?
I finally figured it out! The problem was in my config code and in the way I was decamelizing. Because of inconsistencies in my db field names (most use underscores, but some are already camelCase), I was storing the server's original elem names in an array within the jsonResultsAdapterProvider. But since I was calling jsonResultsAdapterProvider.$get().camelizeKeys(extractedData); within the interceptors, I was reinstantiating the array each time I made a new request. So, the undefined in the PUT request was coming from my decamelizeKeys() method.
My updated config code fixed the problem:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
var jsonAdapter = jsonResultsAdapterProvider.$get();
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
// return extractedData;
return jsonAdapter.camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonAdapter.decamelizeKeys(elem);
});
I am having a bit of difficulty figuring out why I am getting 401 Unauthorized status from service framework. At the moment I have it setup to allow everyone to do as they please but that because when I try to enable authorisation I get 401 error code.
//[SupportedModules("Boards")]
//[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[AllowAnonymous]
public class BoardsServiceController : DnnApiController
{ ... }
The strange thing is I have another module which is more than happy to work away with DnnModuleAuthorize
[SupportedModules("Assignments")]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
public class AsgnsServiceController : DnnApiController
{ ... }
In both cases I have checked to make sure the user has permissions to view the page on which the module lives.
I have cross referenced both projects and everything seems to be spot on. Yet one is working away just fine and the other one returns 401.
Any suggestions?
Update
For Assignments module I am mostly using jQuery style of ajax request just because I haven't got around to revising the module. So a typical GET request would look something like this:
$.ajax({
type: "GET",
url: sf.getServiceRoot( "Assignments" ) + "AsgnsService/GetAssignments",
data: data,
beforeSend: sf.setModuleHeaders
}).done( function ( items ) {
//removed for brevity
}).fail( function ( xhr, result, status ) {
//removed for brevity
});
As for Boards module the code structure is slightly different due knockout implementation. There is a dedicated ServiceCaller but it all boils down to the same ajax call to the server except that instead of having full blown ajax call defined as above it looks much neater.
var that = this;
that.serviceCaller = new dnn.boards.ServiceCaller($, this.moduleId, 'BoardsService');
var success = function (model) {
if (typeof model !== "undefined" && model != null) {
viewModel = new boardViewModel(model.colLists);
ko.bindingHandlers.sortable.beforeMove = viewModel.verifyAssignments;
ko.bindingHandlers.sortable.afterMove = viewModel.updateLastAction;
// normally, we apply moduleScope as a second parameter
ko.applyBindings(viewModel, settings.moduleScope);
}
//console.log('success', model);
};
var failure = function (response, status) {
console.log('request failure: ' + status);
};
var params = {
BoardId: this.boardId
};
that.serviceCaller.get('GetBoardLists', params, success, failure);
And the ServiceCaller ajax function itself looks like this:
function (httpMethod, method, params, success, failure, synchronous) {
var options = {
url: that.getRoot() + method,
beforeSend: that.services.setModuleHeaders,
type: httpMethod,
async: synchronous == false,
success: function (d) {
if (typeof (success) != 'undefined') {
success(d || {});
}
},
error: function (xhr, textStatus, errorThrown) {
if (typeof (failure) != 'undefined') {
var message = undefined;
if (xhr.getResponseHeader('Content-Type').indexOf('application/json') == 0) {
try {
message = $.parseJSON(xhr.responseText).Message;
} catch (e) {
}
}
failure(xhr, message || errorThrown);
}
}
};
if (httpMethod == 'GET') {
options.data = params;
} else {
options.contentType = 'application/json; charset=utf-8';
options.data = ko.toJSON(params);
options.dataType = 'json';
}
$.ajax(options);
};
This would be the two GET requests from two different modules where one is happy and the other throws a status 401 when I enable the same annotations.
Does this provide any clues?
Update
Now in saying all of the above if one takes a look at the original Boards module code base one will notice [DnnAuthorize] annotation attached to every function.
During module revision I removed all instances of [DnnAuthorize] annotation and replaced it with two of my own on the service class itself.
When I add [DnnAuthorize] as annotation on service class itself things work as expected. So why [SupportedModules("Boards")] and [DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)] combination doesn't !?
I am not sure but working with the WebAPI you have to register the Service Framework anti forgery stuff
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
This is part of asking the API to work with a specific module.
I've tried to understand this post regarding this concept, however, I'm failing to get it. I have the following simple setup:
/server/test.js
Meteor.methods({
abc: function() {
var result = {};
result.foo = "Hello ";
result.bar = "World!";
return result;
}
});
/client/myapp.js
var q = Meteor.call('abc');
console.log(q);
This structure returns to the console undefined.
If I change the myapp.js file to:
Meteor.call('abc', function(err, data) {
!err ? console.log(data) : console.log(err);
}
I receive the Object in my console.
Ideally this is what I'd like to be able to do, but it doesn't work, stating in the console: Cannot read property 'greeting' of undefined
/client/myapp.js
var q = Meteor.call('abc');
Template.hello.greeting = function() {
return q.foo;
}
Any help in passing the data from the server object into the template would be greatly appreciated. I'm still learning JavaScript & Meteor.
Thanks!
From the Meteor.call documentation:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
So, you'll want to do it like this:
Meteor.call('abc', function(err, data) {
if (err)
console.log(err);
Session.set('q', data);
});
Template.hello.greeting = function() {
return Session.get('q').foo;
};
This will reactively update the template once the data is available.
This happens because Npm.require has Async behavior. That's the reason that you have to write a callback for Meteor.call.
But there is a solution, just use install(mrt add npm) and you'll get a function named Meteor.sync(//...) with this you can do both games: sync and async in your Meteor.call().
Reference: http://www.sitepoint.com/create-a-meteor-app-using-npm-module/
You can get the return value of a Meteor method for use in a template by using a reactive variable. Check out the working demonstration on Meteorpad
I went for a ghetto solution. But, it works for me, which is what matters, to me. Below is my code, which, in concept, I think, solves OP's problem.
In the client's main.js:
Meteor.setInterval(function() {
confirmLogin();
}, 5000);
This runs the confirmLogin() function every five seconds.
The confirmLogin function (in the client's main.js):
function confirmLogin() {
Meteor.call('loggedIn', function (error, result) {
Session.set("loggedIn", result);
});
}
The loggedIn method (in the server's main.js):
loggedIn: function () {
var toReturn = false;
var userDetails = Meteor.user();
if (typeof userDetails["services"] !== "undefined") {
if (typeof userDetails["services"]["facebook"] != "undefined") {
toReturn = true;
}
}
return toReturn;
},
The relevant helper:
loggedIn: function () {
return Session.get("loggedIn");
}