I'm attempting to create a modular sign in script for some webpages I'm developing. In short, I load the script on the main page, fire the main signIn function from a button press, and an overlay div is created on the main page which is managed by the external signIn.js. The external js sets some sessionStorage variables that will be utilized in the main page.
The hope for modularity would be to have signIn.js handle the authentication from the database and have the main page do with the process of signing in as needed (in this specific instance, it gives users access to their projects). Ideally, the sign in will not force a refresh of the main page due to other project goals.
The problem I'm encountering, is how do I notify the main page that the user has signed in without destroying any sense of modularity?
On top of other efforts, the most hopeful was attempting to create a custom event on the main page's document using $(document).on('userSignedIn', function() {...}); but signIn.js apparently cannot trigger this event.
Any suggestions for how to accomplish this or am I just going about this entirely wrong?
EDIT:
So, this was definitely a scope related issue I was experiencing. To flesh out the process, if anyone finds it relevant, signIn.js adds an overlay div to mainPage.html. $("#signInContainerDiv").load("signIn.html") is used to load the sign in form into the page. It turns out, when I was trying to reference $(document), it was using signIn.html's document, and not mainPage.html's. Upon that realization, I just created a div (signInNotify) on the mainPage that I bind the event to ($("#signInNotify").on("userSignedIn", function() {...});) and trigger it in signIn.js.
My own inexperience has conquered me, yet again.
jQuery can help you out when it comes to this. Here's an example from the main page for trigger
$( "#foo" ).on( "custom", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
$( "#foo").trigger( "custom", [ "Custom", "Event" ] );
jQuery Page Reference
Another solution is to use some library like amplify.js, it has publish/subscribe functionality which can be useful for implementing the "observer pattern". You could also implement your own library for that, the code could be something like this:
// the implementation
function Notify () {
this.listeners = {};
}
Notify.prototype.subscribe = function (event, callback, context) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push({ callback: callback, context: context || null});
};
Notify.prototype.publish = function (event/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1);
(this.listeners[event] || []).forEach(function (x) {
x.callback.apply(x.callback.context, args);
});
};
// usage:
// an instance, or can be implemented as a singleton
var global_events = new Notify();
// wherever you want to be notified of login events
global_events.subscribe('login_success', function () {
// do something with the arguments
}, myContext/*optional*/);
// after success login
global_events.publish('login_success', user_credentials, other_data);
// and all subscribers (listeners) will be called after this
I have used that code for similar purposes and also used amplifyjs a couple times, you can read more about Amplify Pub/Sub.
Related
I have a cordova application that uses push notification (still using the old plugin :-().
The application uses ngRouter and the navigation is relatively basic - in that I mean that my main menu changes ngView but popups/modals are not part of the navigation and are either triggered by some bound controller property or through a call to a controller function (e.g. $scope.openMyModal).
I am trying to be able to call such function on one of my controllers after I received push notification (and the controller is loaded).
I implemented some code using a timeout to broadcast an event which should be caught in the relevant controller and open the modal. Roughly the code is:
In app.js:
onNotification() {
// some code for determining the type of notification
// then
setTimeout(function() {
$rootScope.$broadcast("someEventCode");
}, 10); // or 1000 in case of cold start
}
In MyController.js:
.controller('MyController', function($scope, $rootScope, $modal,...) {
$scope.openMyModal = function() { // open modal using $model }
$scope.on("someEventCode", function() {
$scope.openMyModal();
});
}
This kind of works but is not consistent/deterministic. For example, in slower devices it may broadcast before the controller is ready to respond to it.
I also tried to set some variable on root scope (in onNotification) and in the controller create a function which is called from the markup (e.g. {{isNotificationReady()}}) but this also doesn't work well.
Another approach was to use double notifications - set a flag in root scope when the notification arrives, wait for an event from the target controller (indicating it is loaded) and then, at $rootScope again, if flag is set, broadcast the "open dialog" event (and delete the flag). Following this approach, I am not sure how to trigger the "loaded" event so I use a function from the markup:
In MyController.js:
$scope.isLoaded = function() {
$scope.$emit("myControllerLoaded");
}
In markup:
<div><!-- the content --></div>
{{isLoaded()}}
In app.js
$rootScope.$on("myControllerLoaded", function(event) {
if ($rootScope.notification === "someEventCode") {
$rootScope.$broadcast("openTheModel");
delete $rootScope.notification;
}
});
This seems like cumbersome and inefficient code. isLoaded() is called multiple times (not sure why) and it is kind of spaghetti code.
My question is - how should I implement something like that in a clear and efficient manner? Just a reminder, the app could be "cold started" or in the background and I need to know when it is "running" (or the controller is ready).
I've found a slightly more robust, timeout based implementation (still not exactly what I was hoping for).
The idea is to set a flag and send (broadcast) the signal after some time. Then resend the signal on interval until the flag is unset by the target controller:
In app.js
function broadcastSomeEvent() {
$rootScope.$broadcast("someEventCode");
if ($rootScope.eventFlag) {
setTimeout(broadcastSomeEvent, 50);
}
}
onNotification() {
// some code for determining the type of notification, then
$rootScope.eventFlag = true;
setTimeout(broadcastSomeEvent, 10); // or 1000 in case of cold start
}
In MyController.js
$scope.$on('someEventCode', function() {
delete $rootScope.eventFlag; // delete flag so event is stopped
$scope.openMyModal();
});
This is still an iff-y implementation to my taste. Even though it does work for both cold start and when the application is in the background I believe that it is not robust as it should.
Still, I wouldn't mark this solution as "the answer".
On the other hand, with no proper state routing, maybe there's not much more than can be done.
My issue:
I have created a JavaScript class that is used by our dev team across our site. It is essentially functionality for a grid/table like structure that allows the user to select items and perform actions on these items with provided action buttons.
Action button workflow:
User clicks action button
Popup appears: "Are you sure you want to perform this action on these items?"
User clicks "Yes": AJAX call is made and popup closes upon AJAX success.
User clicks "No": Popup closes.
Right now, these action buttons are individually bound in jQuery by our Devs on each page that needs it. Any given page could have a handful of event bindings.
After successful completion of any of these actions, I would like to run Grid.afterActionComplete() from any given instantiation. I would like to run Grid.afterActionComplete() within the actions AJAX success callback. I know I could expose (return) afterActionComplete in my class and have the Devs run the function themselves, but this is not ideal.
My requirements:
Would like to keep the amount of additional code for Devs to a minimum
Many AJAX request can be made from any given page (some from non-action buttons), so using a global ajaxSuccess event wouldn't necessarily work. Plus, I would hate to use an event with that global of a scope.
My question is two-fold:
How could I dynamically bind Grid.afterActionComplete() to any given action's AJAX success callback? (if possible)
How would I best incorporate the action bindings into the Grid class upon instantiation to further encapsulate my code?
My sample code:
/* [START] Pre-existing code */
var Grid = function(gridID){
var gridID = $(gridID),
afterActionComplete = function(){
// Ideally, I'd like to bind the function here
},
refresh = function(){
// Refresh grid
},
return {
refresh : refresh
}
}
var popup = function(){
$('.popup').show();
// Pops up a window with an Action button and Cancel button
// Just a placeholder to help explain concept
}
/* [END] Pre-existing code */
/*
[START] Dev defined code
Devs will be creating these event bindings across the
site.
*/
var myGrid = new Grid("#grid1");
$('#actionPopupButton').click(function(){
popup();
$('.popup #actionButton').click(function(){
$.post( "ajax/test.html", function( data ) {
myGrid.refresh();
$('.popup').hide();
// I'd like to inject Grid.afterActionComplete() here
// Maybe create custom event and trigger() it here?
// Ideally, I would love to not require the Devs insert additional code hre, but I'm not sure that's possible
});
});
});
/* [END] Dev defined code */
I've been pondering these questions for a week or so now, and would love any suggestions to help me solve this issue. Thanks!
Assuming all of the "developer code" is very similar, I would think ideally you would want to have the developers pass in appropriate parameters instead of create a bunch of very similar code.
For instance, if you made the popup method part of Grid and has the url and callback passed to the function you could do something like this:
popup = function(url, callback){
var that = this;
$('.popup').show();
$('.popup #actionButton').click(function(){
$.post( url, function( data ) {
// call the passed in callback
callback(data);
// do your post-callback stuff
that.refresh(); // assuming this happens in every callback
$('.popup').hide(); // assuming this happens in every callback
that.afterActionComplete();
});
});
}
Then your example developer code would become this:
var myGrid = new Grid("#grid1");
$('#actionPopupButton').click(function(){
myGrid.popup("ajax/test.html", function(data){
// do instance-specific stuff here
});
});
Correct me if I am wrong. You want Grid.afterActionComplete() called only on specific AJAX requests, correct? This is why you cannot use .ajaxSuccess()? If that is the case, the best thing you can do is to trigger a custom event.
If you feel that is too much work for the developers, you can abstract the $.post functionality inside a custom function of your Grid class. After you execute the callback, you can then make the call to Grid.afterActionComplete(). If it is mandatory that Grid.afterActionComplete() be called after those requests, it would make more sense to take this route since it seems to be part of the contract. This way you can protect the developers from themselves (i.e., if they forgot to call the function or trigger the custom event) by making it so that they can only make the post using the Grid API.
im having an issue with PubSub in Javascript.
im trying to figure out why $.subscribe is not printing the value. I assume its because of scope between $.publish and $.subscribe.
i would like to have other modules subscribe to the event. how would i do that?
i put an example on jsfiddle:
http://jsfiddle.net/Fvk2G/
window.MQ = (function (window, document, undefined) {
"use strict";
function MQ() {
testPubSub();
function testPubSub() {
$.publish("test");
}
}
return MQ
})(this, this.document);
var mq = new MQ();
$.subscribe("test", function () {
console.log("print value");
});
thanks
pete
You've set up a system that uses jQuery event handling for relaying messages, which is not in itself a bad idea. If you expect that it will save events that were triggered and report them to subsequent "subscribers", however, you've made an incorrect assumption about the semantics of the event mechanism. If a tree falls in the forest, the forest doesn't retain the sound until your hiking party arrives. Similarly, an event that's triggered with no listeners is just forgotten.
If you move your code that creates the "MQ" to after the subscription is done, then it works fine.
I am having trouble figuring out how to use dojo/aspect with widgets.
Consider the following:
require( [ 'dijit/form/Button' ],
function( Button)
{
var myButton = new Button({label: 'Click me!'});
} );
How would I connect to the button's postCreate() or startup() methods to discover when it has been rendered?
There seems to be no point when I can add advice to a method. See the comments, here:
require( [ 'dijit/form/Button', 'dojo/aspect' ],
function( Button, aspect )
{
// I cannot use aspect.after() here as I have no instance yet
var myButton = new Button({label: 'Click me!'});
// ...but I cannot do it here either as the lifecycle has already kicked off
} );
(The button is just to make it easier to explain the issue. My real-world problem involves widgets that contain other widgets, so I need to know when the whole lot have rendered before performing an action).
By instantiating the widget programmatically, the postCreate method of the widget is implicitly being called. As far as I know there isn't an easy (or really a good reason to) to connect to the postCreate stage in the widget lifecycle.
startup, on the other hand you need to call explicitly when programmatically instantiating a widget:
var myButton = new Button({label: 'Click me!'});
aspect.after(myButton, 'startup', function(){
console.log('startup called');
});
//place button in page (since startup implies the widget has been placed in the page
myButton.placeAt(someDomNode)
myButton.startup();
If you want to do work during the postCreate lifecycle of a widget, you'll likely want to subclass that widget. Doing so would look something like this:
//in a file like my/custom/widget.js
define(['dojo/_base/declare','dijit/form/Button'],function(declare,Button){
return declare('my.custom.widget',[Button],{
postCreate:function(){
//we still want to call the parent class's postCreate function
this.inherited(arguments);
//create some other widgets as well
}
});
});
I hope I did my homework well, searching the Internets for the last couple of hours and trying everything before posting here, but I'm really close to call it impossible, so this is my last resort.
I want a simple thing (but seems like hard in JavaScript):
Click button -> Open Window (using window.open)
Perform an action in the popup window and return the value to parent (opener)
But I want to achieve it in a systematic way, having a callback defined for this popup; something like:
var wnd = window.open(...)
wnd.callback = function(value) {
console.log(value);
};
I've tried defining the callback property in popup window JS code:
var callback = null;
Unfortunately, that does not work, as...
$('#action').click(function() {
console.log(callback);
});
... returns just that "null" I set initially.
I've also tried setting the callback in a parent window after window load (both thru window.onload=... and $(window).ready()), none worked.
I've also tried defining some method in child window source code to register callback internally:
function registerCallback(_callback)
{
callback = _callback; // also window.callback = _callback;
}
But with the same result.
And I don't have any more ideas. Sure, it would be simple setting the value using window.opener, but I'll loose much of a flexibility I need for this child window (actually an asset selector for DAM system).
If you have some ideas, please share them.
Thank you a million!
HTML5's postMessage comes to mind. It's designed to do exactly what you're trying to accomplish: post messages from one window and process it in another.
https://developer.mozilla.org/en/DOM/window.postMessage
The caveat is that it's a relatively new standard, so older browsers may not support this functionality.
http://caniuse.com/#feat=x-doc-messaging
It's pretty simple to use:
To send a message from the source window:
window.postMessage("message", "*");
//'*' is the target origin, and should be specified for security
To listen for messages in a target window:
window.addEventListener
("message", function(e) {
console.log(e.data); //e.data is the string message that was sent.
}, true);
After few more hours of experiments, I think, I've found a viable solution for my problem.
The point is to reference jQuery from parent window and trigger a jQuery event on this window (I'm a Mac user but I suppose, jQuery has events working cross-platform, so IE compatibility is not an issue here).
This is my code for click handler on anchor...
$(this).find('a[x-special="select-asset"]').click(function() {
var evt = jQuery.Event('assetSelect', {
url: 'this is url',
closePopup: true,
});
var _parent = window.opener;
_parent.jQuery(_parent.document).trigger(evt);
});
... and this is the code of event handler:
$(document).bind('assetSelect', function (evt) {
console.log(evt);
});
This solution is fine, if you don't need to distinguish between multiple instances of the asset selection windows (only one window will dispatch "assetSelect" event). I have not found a way to pass a kind of tag parameter to window and then pass it back in event.
Because of this, I've chosen to go along with (at the end, better and visually more pleasant) solution, Fancybox. Unfortunately, there is no way - by default - to distinguish between instances either. Therefore, I've extended Fancybox as I've described in my blog post. I'm not including the full text of blog post here, because is not the topic of this question.
URL of the blog post: http://82517.tumblr.com/post/23798369533/using-fancybox-with-iframe-as-modal-dialog-on-a-web