How to broadcast to other controllers when load with module.config or .run in Angularjs - module

I have a checking when reading the web page,then using the result to refresh sidebar by ng-repeat,but I have errors :
Uncaught Error: Unknown provider: $scope from myModule or
Uncaught Error: Unknown provider: $scope from sharedService
How can I resolve it?
Here is my code
module:
var myModule = angular.module('myModule', []);
service for broadcast:
myModule.factory('mySharedService', function($rootScope) { //service
var sharedService = {};
sharedService.keyHistory = [];
sharedService.linkHistory = [];
sharedService.prepForBroadcast = function(key,link) {
this.keyHistory = key;
this.linkHistory = link;
this.broadcastItem();
};
sharedService.prepForBroadcastAdd =function(key){
console.log(this.keyHistory.push(key));
//this.linkHistory = linkHistory+link;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
config to do Checking:
myModule.config(function($scope,sharedService){
$.ajax({
url:"/fly/AJAX",
type:"POST",
contentType:'application/x-www-form-urlencoded; charset=UTF-8',
datatype:"json",
success:function(data){
if(data!=null){
var loginResult = $.parseJSON(data);
if (loginResult.success == true){
console.log("login success");
$("#userLable").html(loginResult.userName+'('+loginResult.loginID+')');//
if (loginResult.hasHistory==true) {
sharedService.prepForBroadcast(loginResult.searchHistory,[]);
console.log("broadcast");
}
};
}
}
});
});
SideCtrl:
function SideCtrl($scope,sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.keyHistory =sharedService.keyHistory;
$scope.linkHistory = sharedService.linkHistory;
});
}
SideCtrl.$inject = ['$scope', 'mySharedService'];
THX !

The error is due to trying to request a $scope in a config block, which you can't do. If I understand what you're trying to do, then I also think you're over-complicating it. I'd solve the problem a little differently. The details would depend on your requirements and use case, but based on the information you gave...
I'd have a service responsible for communication with the server and storing the state:
app.factory( 'loginService', function ( $http ) {
var result;
function doRequest( data ) {
// just flesh out this post request to suit your needs...
return $http.post( '/fly/ajax', data, {} )
.then( function ( response ) {
// assuming you don't care about the headers, etc.
return response.data;
});
}
// Do it once initially
if ( ! angular.isDefined( result ) ) {
result = doRequest();
}
// return the service's public API
return {
getStatus: function () { return result; },
login: doRequest
};
});
Now the first time this service is requested, the $http request will be made. If you're accessing this from multiple controllers, the post will only occur once because of the isDefined statement. You can then use this in your controllers:
app.controller( 'MainCtrl', function( $scope, loginService ) {
loginService.getStatus().then( function ( data ) {
// do whatever you need to with your data.
// it is only guaranteed to exist as of now, because $http returns a promise
});
});
Every controller accesses it the same way, but it was still only called once! You can set values against the scope and access it from your views, if you want:
app.controller( 'MainCtrl', function( $scope, loginService ) {
loginService.getStatus().then( function ( data ) {
$scope.loginId = data.loginID;
});
});
And in your view:
<h1>Welcome, {{loginId || 'guest'}}!</h1>
And if you need to, you call the function again:
app.controller( 'MainCtrl', function( $scope, loginService ) {
// ...
loginService.login( $scope.user ).then( function ( data ) {
$scope.loginId = data.loginID;
});
// ...
});
As you can see, broadcasting an event is totally unnecessary.

I would do it differently. I would create some sort of more top-level controller, like function MainController($rootScope, $scope, sharedService) and wire it up with body: <body ng-controller='mainController' ng-init='init()'. After that you should create init() method in MainController.
Inside this initialization method I would call sharedService which should make AJAX request (via $http! that's the best practice, and it's very similar to jQuery) and broadcast proper event when required.
That way you make sure to call initialization just once (when MainController is initializing), you stick to the angular's best practices and avoid dodgy looking code.

Related

TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished

This seems to be a Safari only bug. It does not occur in Chrome as far as I can tell. I have a very standard IndexedDB setup. I call initDb, save the result, and that provides me a nice way to make calls to the DB.
var initDb = function() {
// Setup DB. whenDB is a promise we use before executing any DB requests so we know the DB is fully set up.
parentDb = null;
var whenDb = new Promise(function(resolve, reject) {
var DBOpenRequest = window.indexedDB.open('groceries');
DBOpenRequest.onsuccess = function(event) {
parentDb = DBOpenRequest.result;
resolve();
};
DBOpenRequest.onupgradeneeded = function(event) {
var localDb = event.target.result;
localDb.createObjectStore('unique', {
keyPath: 'id'
});
};
});
// makeRequest needs to return an IndexedDB Request object.
// This function just wraps that in a promise.
var request = function(makeRequest, key) {
return new Promise(function(resolve, reject) {
var request = makeRequest();
request.onerror = function() {
reject('Request error');
};
request.onsuccess = function() {
if (request.result == undefined) {
reject(key + ' not found');
} else {
resolve(request.result);
}
};
});
};
// Open a very typical transaction
var transact = function(type, storeName) {
// Make sure DB is set up, then open transaction
return whenDb.then(function() {
var transaction = parentDb.transaction([storeName], type);
transaction.oncomplete = function(event) {
console.log('transcomplete')
};
transaction.onerror = function(event) {
console.log('Transaction not opened due to error: ' + transaction.error);
};
return transaction.objectStore(storeName);
});
};
// Shortcut function to open transaction and return standard Javascript promise that waits for DB query to finish
var read = function(storeName, key) {
return transact('readonly', storeName).then(function(transactionStore) {
return request(function() {
return transactionStore.get(key);
}, key);
});
};
// A test function that combines the previous transaction, request and read functions into one.
var test = function() {
return whenDb.then(function() {
var transaction = parentDb.transaction(['unique'], 'readonly');
transaction.oncomplete = function(event) {
console.log('transcomplete')
};
transaction.onerror = function(event) {
console.log('Transaction not opened due to error: ' + transaction.error);
};
var store = transaction.objectStore('unique');
return new Promise(function(resolve, reject) {
var request = store.get('groceryList');
request.onerror = function() {
console.log(request.error);
reject('Request error');
};
request.onsuccess = function() {
if (request.result == undefined) {
reject(key + ' not found');
} else {
resolve(request.result);
}
};
});
});
};
// Return an object for db interactions
return {
read: read,
test: test
};
};
var db = initDb();
When I call db.read('unique', 'test') in Safari I get the error:
TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished
The same call in Chrome gives no error, just the expected promise returns. Oddly enough, calling the db.test function in Safari works as expected as well. It literally seems to be that the separation of work into two functions in Safari is somehow causing this error.
In all cases transcomplete is logged AFTER either the error is thrown (in the case of the Safari bug) or the proper value is returned (as should happen). So the transaction has NOT closed before the error saying the transaction is inactive or finished is thrown.
Having a hard time tracking down the issue here.
Hmm, not confident in my answer, but my first guess is the pause that occurs between creating the transaction and starting a request allows the transaction to timeout and become inactive because it finds no requests active, such that a later request that does try to start is started on an inactive transaction. This can easily be solved by starting requests in the same epoch of the javascript event loop (the same tick) instead of deferring the start of a request.
The error is most likely in these lines:
var store = transaction.objectStore('unique');
return new Promise(function(resolve, reject) {
var request = store.get('groceryList');
You need to create the request immediately to avoid this error:
var store = transaction.objectStore('unique');
var request = store.get('groceryList');
One way to solve this might be simply to approach the code differently. Promises are intended to be composable. Code that uses promises generally wants to return control to the caller, so that the caller can control the flow. Some of your functions as they are currently written violate this design pattern. It is possible that by simply using a more appropriate design pattern, you will not run into this error, or at least you will be able to identify the problem more readily.
An additional point would be your mixed use of global variables. Variables like parentDb and db are just going to potentially cause problems on certain platforms unless you really are an expert at async code.
For example, start with a simple connect or open function that resolves to an open IDBDatabase variable.
function connect(name) {
return new Promise(function(resolve, reject) {
var openRequest = indexedDB.open(name);
openRequest.onsuccess = function() {
var db = openRequest.result;
resolve(db);
};
});
}
This will let you easily compose an open promise together with code that should run after it, like this:
connect('groceries').then(function(db) {
// do stuff with db here
});
Next, use a promise to encapsulate an operation. This is not a promise per request. Pass along the db variable instead of using a global one.
function getGroceryList(db, listId) {
return new Promise(function(resolve, reject) {
var txn = db.transaction('unique');
var store = txn.objectStore('unique');
var request = store.get(listId);
request.onsuccess = function() {
var list = request.result;
resolve(list);
};
request.onerror = function() {
reject(request.error);
};
});
}
Then compose it all together
connect().then(function(db) {
return getGroceryList(db, 'asdf');
}).catch(error);

Override/Intercept XMLHttpRequest response in all browsers

What do I want to achieve ?
I want to intercept the XMLHttpRequest and modify the response for some particular requests. (For ex. decrypt content and assign it to back response)
What I have done so far ?
Below code intercepts the request and modifies the response. It works in all browsers (Chrome, Firefox, Opera, Edge) except IE 11.
const dummySend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
const _onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200 || this.status === 1223) {
// as response is read-only and configurable, make it writable
Object.defineProperty(this, 'response', {writable: true});
this.response = modifyResponse(this.response);
}
}
if (_onreadystatechange) {
_onreadystatechange.apply(this, arguments);
}
}
dummySend.apply(__self, arguments);
}
What is the Issue ?
All of that doesn't work only in IE 11, The Error thrown is 'TypeError: Assignment to read-only property is not allowed in strict mode'.
Can someone please help me with this ?
I could do it the other way, which is to have a dummy XMLHttpRequest object exposed to the original requester and then handle the actual XMLHttpRequest yourself. Please read code for more clarity.
let oldXMLHttpRequest = window.XMLHttpRequest;
// define constructor for XMLHttpRequest proxy object
window.XMLHttpRequest = function() {
let _originalXhr = new oldXMLHttpRequest();
let _dummyXhr = this;
function decryptResponse(actualResponse) {
return base64Decrypted = decrypt(response, secret);
}
_dummyXhr.response = null;
// expose dummy open
_dummyXhr.open = function () {
const _arguments = [].slice.call(arguments);
// do any url modifications here before request open
_dummyXhr._url = _arguments[1];
return _originalXhr.open.apply(_originalXhr, _arguments);
};
// expose dummy send
_dummyXhr.send = function () {
let _onreadystatechange = _dummyXhr.onreadystatechange;
_originalXhr.onreadystatechange = function() {
if (this.readyState === 4 && (this.status === 200 || this.status === 1223)) {
_dummyXhr.response = decryptResponse(this.response);
}
// call callback that was assigned on our object
if (_onreadystatechange) {
_onreadystatechange.apply(_dummyXhr, arguments);
}
}
_originalXhr.send.apply(_originalXhr, arguments);
};
// iterate all properties in _originalXhr to proxy them according to their type
// For functions, we call _originalXhr and return the result
// For non-functions, we make getters/setters
// If the property already exists on _dummyXhr, then don't proxy it
for (let prop in _originalXhr) {
// skip properties we already have - this will skip both the above defined properties
// that we don't want to proxy and skip properties on the prototype belonging to Object
if (!(prop in _dummyXhr)) {
// create closure to capture value of prop
(function(prop) {
if (typeof _originalXhr[prop] === "function") {
// define our own property that calls the same method on the _originalXhr
Object.defineProperty(_dummyXhr, prop, {
value: function() {return _originalXhr[prop].apply(_originalXhr, arguments);}
});
} else {
// define our own property that just gets or sets the same prop on the _originalXhr
Object.defineProperty(_dummyXhr, prop, {
get: function() {return _originalXhr[prop];},
set: function(val) {_originalXhr[prop] = val;}
});
}
})(prop);
}
}

How can I compose handler off Hapijs?

I need to do some additional authentication in a few of my handlers. Is there a way of doing that way in a composable way?
export async function handler(request) {
const user = request.auth.credentials;
const customer = FindCustomer(user);
if (!customer) {
throw Boom.forbidden('user is not a customer');
}
if (!customer.verified) {
throw Boom.forbidden('customer not validated');
}
// actual handler logic
}
Is there a way to wrap this so that some routes already provide the customer in the request object ?
You can make use of the extension points in the request life cycle. In your case, the 'onPostAuth' extension point would be ideal.
server.ext('onPostAuth', function (request, reply){
const user = request.auth.credentials;
const customer = FindCustomer(user);
if (!customer) {
return reply (Boom.forbidden('user is not a customer'));
}
if (!customer.verified) {
return reply(Boom.forbidden('customer not validated'));
}
reply.continue();
});
Complementing ZeMoon's answer, you can implement the onPostAuth like this:
server.ext('onPostAuth', function (request, reply) {
if(request.route.settings.plugins.verifyCustomer) {
const user = request.auth.credentials;
const customer = FindCustomer(user);
if (!customer) {
return reply (Boom.forbidden('user is not a customer'));
}
if (!customer.verified) {
return reply(Boom.forbidden('customer not validated'));
}
}
reply.continue();
});
And then add a configuration plugins.verifyCustomer to the route:
server.route({
method: 'get',
path: '/test1',
handler: function(request, reply) {
// Handler logic here
},
config: {
plugins: {
verifyCustomer: true
}
}
});
i think a more robust way would be to assign scope to the credentials when the customer is authenticated, and to require the scope in the routes you want it.

How to pull data from SqlDataAdapter and store it in a JsonStore collection in MobileFirst

I have a SqlDataAdapter and I want to store it in a JsonStore collection in MobileFirst and Display it in table form. I have tried using Load() method but its not working.
this is my resultSetCollection.js file
;(function () {
WL.JSONStore.init({
resultSet : {
searchFields: {"EMP_NAME":"string","EMP_ID":"integer"}
}
}, {
// password : 'PleaseChangeThisPassword'
})
.then(function () {
return WL.Client.invokeProcedure({
adapter : 'EmployeeList',
procedure : 'getEmployeeLists',
parameters : []
});
})
.then(function (responseFromAdapter) {
alert('responseFromAdapter:' + JSON.stringify(responseFromAdapter.invocationResult.resultSet));
var accessor = WL.JSONStore.get('resultSet');
var data=responseFromAdapter.invocationResult.resultSet;
var changeOptions = {
replaceCriteria : ['EMP_ID', 'EMP_NAME'],
addNew : true,
markDirty : false
};
return accessor.change(data, changeOptions);
})
.then(function (response) {
console.log(response);
//Here I want to retrieve the collection and display it in a table
})
.fail(function (errObj) {
WL.Logger.ctx({pretty: true}).error(errObj);
});
}());
An adapter procedure request from a client application will have a response object in its success and failure callbacks. So lets assume that the request was successfull and data was returned from the backend server.
Lets also assume you have a JSONStore initialized and properly setup with a collection. You then only need to get the collection and add data to it.
The below example takes the full response from an HTTP adapter request and puts it as-is into a collection. You will of course need to create a better setup for your specific scenario...
Note that the code is not optimised and performance or with 100% proper logic. It's just a demonstration flow.
Tested in MobileFirst Platform Foundation 7.0.0.00.
main.js:
var collectionName = 'mydata';
var collections = {
mydata : {
searchFields : {data: 'string'},
}
};
function wlCommonInit(){
WL.JSONStore.init(collections).then(
function() {
var resourceRequest = new WLResourceRequest("/adapters/myadapter/getStories", WLResourceRequest.GET);
resourceRequest.send().then(resourceRequestSuccess, resourceRequestFailure);
}
);
}
function resourceRequestSuccess(response) {
WL.JSONStore.get(collectionName).add(response).then(
function(){
WL.Logger.info("successfully added response to collection");
displayDataFromCollection();
},
function() {
alert("failed adding response to collection");
}
);
}
function resourceRequestFailure() {
alert ("failure");
}
If you then like to fetch the data from the JSONStore and display it in the HTML, you could do something like this:
// get a specific item from the stored response and display it in a table
function displayDataFromCollection() {
WL.JSONStore.get(collectionName).findAll().then(
function(result) {
$("#mytable").append("<tr><td>" + result[0].json.responseJSON.rss.channel.title + "</td></tr>");
},
function() {
alert ("unable to display collection");
}
);
}
The index.html looks like this:
<table id="mytable">
</table>

Accessing ioArgs from callback functions

I'm upgrading a bunch of old dojo to 1.8. For our ajax request handling we've got a decorator (well, function wrapper) that will perform redirects in certain cases based on the response content, for example:
// Decorator func:
var redirectDecorator = function(func) {
var f = function(data, ioArgs) {
if(data.redirect) {
// A manual location redirect:
window.location.href = data.redirect;
if(data.redirect_xhr) {
// clone ioArgs, spawn new request to follow redirect etc
// <snip>
} else {
func(response);
}
}
return f;
}
// Used like so:
dojo.xhrPost({
url: url
handleAs: "json",
form: form,
load: redirectDecorator(function(data, ioArgs) {
// do stuff
})
});
Now, in dojo 1.8 (the dojo/request/xhr module) xhr() returns a Deferred for chaining and the callbacks are only supplied the data argument (no ioArgs - apparently these are attached to the promise - see http://bugs.dojotoolkit.org/ticket/12126).
In other words, the above ajax call becomes:
xhr.post(url, {
handleAs: "json",
form: form
}).then(function(data) {
// do stuff
});
Problem is, I can no longer wrap the anonymous function because ioArgs are not supplied. Inspecting the deferred (by breaking the chaining) doesn't appear to work either and would require more re-engineering than I'd like.
Any ideas?
Thanks Ken (for your help at #dojo too). To elaborate, the solution is to use dojo/request and use the .response deferred promise instead, which provides the necessary info:
// Decorator func:
var redirectDecorator = function(func) {
var f = function(response) {
var data = response.data;
if(data.redirect) {
// A manual location redirect:
window.location.href = data.redirect;
if(data.redirect_xhr) {
request(data.redirect_xhr, response.options).then(func);
} // more conditions follow.
}
return f;
}
request.post(url, {
handleAs: "json",
form: form
}).response.then(redirectDecorator(function(response) { // <-- note .response.then(
// do stuff where data is response.data
}));
Promises returned from dojo/request are actually objects with an additional response promise that provides more information. See the following places for information:
http://www.sitepen.com/blog/2012/08/21/introducing-dojorequest/
http://dojotoolkit.org/reference-guide/1.8/dojo/request.html
http://dojotoolkit.org/documentation/tutorials/1.8/ajax/