I have an application where the user can edit content, then preview that content. Before preview, I need to have the content saved ($http.post to the server). I'm using angular and because the save routine is run through a different controller the code to save looks like:
$scope.$broadcast('save');
var saveCompletedListener = $scope.$on('saveCompleted', function () {
// this call doesn't work (because it waits for data save):
window.open($scope.contentUrl);
});
// this call does work, but shows old data in the popup:
window.open($scope.contentUrl);
The problem is the window.open call is getting blocked in all browsers. If I just call the window.open without waiting for save, it opens fine (it just shows the content from the previous save). So, is there some way in angular to show the popup, but have it wait to load the url until save has completed?
Related
I'm writing a small script to capture link clicks and save the link's URL into a database table in order to track how many times each link on a particular page is clicked. The links are to external sites.
So, I capture the click event in my JavaScript function, use jQuery to post to a PHP page that saves the data in MySQL, and then the JavaScript function redirects the user to the URL of the link they clicked on.
The problem I'm running into is that it seems like the post never completes because the of redirect. I've worked around this by calling the redirect inside the post callback, but that adds a few second delay because it doesn't get called until after the post completes. I'm looking for a way to just post the data and redirect the user immediately, regardless of whether or not the post succeeds or fails.
This is the current code with the workaround. It works fine, but adds the delay:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
},
function( response )
{
window.location = clicked;
}
);
event.preventDefault();
}
And this is what I'd like to do, but when I try it never completes:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
}
);
window.location = clicked;
event.preventDefault();
}
jQuery doesn't provide a callback for what you're looking for. Here are the available ready states:
Value State Description
0 UNSENT open()has not been called yet.
1 OPENED send()has not been called yet.
2 HEADERS_RECEIVED send() has been called, and headers and status are available.
3 LOADING Downloading; responseText holds partial data.
4 DONE The operation is complete.
You're looking for readystate 2, as that's the earliest you're aware of the fact that the server received the message.
This should get you off the ground:
var xhr = new XMLHttpRequest();
xhr.open("POST", clicked);
xhr.onreadystatechange = function() {
if (xhr.readyState >= 2) window.location = clicked;
};
xhr.send($('#track-click-post-url').attr('value'));
https://developer.mozilla.org/en/XMLHttpRequest for further reading.
Why do you post using Javascript when you are going to load a page any way?
Just update the db with the link clicked on the new page.
Perhaps using the referrer URL to track on what page the click was.
Or some other solution to get on what page the click was (e.g. url param) or some other way.
When you leave a page, all pending requests are killed, and the new page loads. The 1st way is the correct way. Yes, there will be a delay when a link is clicked, that's because the POST request is running.
You can't run a request in the background of a page, if the user is not on that page.
Maybe you can save the link URL in a cookie, and put it into the DB when the next page loads.
I'm using angular-seed template (AngularJS) and I have routeProvider setup and such, now I want to execute a function (which resides in a factory), but only the very first time when the page gets loaded. I found many solutions for this, but I don't want to execute the code each time the users switches between tabs (via routeProvider of course, page doesn't get reloaded) - the code must be executed only when the whole page gets (re)loaded.
How should I approach this? I tried to call the function from run and then broadcast the event when page gets loaded, but there are no event listeners - I guess that is because the run part gets executed before the controllers are setup, so there are no listeners attached at the time when the event gets broadcast.
So, any suggestions how to approach this?
UPDATE
Use case:
when user types the url in the page, the page gets loaded
when pages gets loaded, a $http.get request is performed, which gets a random content
this content can be changed only by clicking a button, to explicitly request a change of content.
if users clicks to a different page e.g. view2 (routeProvider) and then back to the view1, the content must not change
when users refreshes the page (F5), the content changes again (or as already stated, by a click of a button)
Use the run method:
app.run(function(yourService){
yourService.cacheRandomContent();
});
It runs only once after the app is bootstrapped and services are created.
To access the data in controller, just inject the same service:
app.controller('someCtrl',function($scope, yourService){
yourService.getCachedRandomContent().then(function(resp){
$scope.data = resp.data;
});
});
Your service would be something like:
app.service('yourService',function($http){
var promise;
return {
cacheRandomContent: function(){
promise = $http.get();
},
getCachedRandomContent : function(){
return promise;
}
}
});
I have the requirement to allow a user to select a file to upload and then need to capture additional required information from the user that applies to that specific file that will be sent as params to the server when uploading.
Thus far, I have been doing this by working off of the fine-uploader onSubmit callback and displaying a modal with the inputs I need. If inputs pass validation, I store the file id and associated params, otherwise, if they close/cancel the modal I call the fine-uploader cancel api and cancel the file with the given id.
The user can do this multiple times, which queues the files into the standard fine-uploader list and then they would press "Upload" to actually start the uploads concurrently.
All of this works great, my problem is that because I have the "multiple" option enabled, the user can select or drag multiple files at the same time, but I need to present the modal to each of them individually. The onSubmit event is firing individually for each of the files so how can I prevent the second, third, etc, modal from opening until I either cancel or save the previous one?
For the record I am actually using Backbone/Marionette and triggering a "file:added" event for each onSubmit callback. This is where I am creating the modal view and displaying it, however, the solution doesn't need to be tied to backbone.
Also, if there is a better way to collect parameters for a file using fine-uploader, I am not necessarily tied to this approach - just need to make sure that each file has the additional data populated otherwise it can not be uploaded.
Here is the Fine Uploader onSubmit callback:
.on('submit', function(event, id, name) {
var args = {};
args.id = id;
args.fileName = name;
uploadDocsVent.trigger('file:added', args);
})
Here is my code that is listening for that event, creating a File Backbone model, and instantiating a view to be displayed in a Modal region.
uploadDocsVent.on("file:added", function(args){
var file = new UploadDocuments.File( args );
var addlInfoView = new UploadDocuments.Views.FileInfo({
model: file,
categories: App.uploadedFilesCategories
});
App.appLayout.modalConfirmRegion.show(addlInfoView);
});
In my view I have this code responding to the modal clicks:
onDialogPrimaryClicked: function () {
UploadDocuments.claimsUploadWidget.addFileWithInfo( this.model );
this.trigger("dialog:close");
},
onDialogSecondaryClicked: function () {
UploadDocuments.uploadView.uploader.fineUploader('cancel', this.model.get('id'));
this.trigger("dialog:close");
}
Thanks in advance for any ideas on how to approach/get around this.
Andrew - I think I know what you are getting at. It's clear that you simply want Fine Uploader to only process a file in a batch of selected files after the user has submitted information in the modal that your display for that file in your onSubmit handler. What you are looking for is a way to return a promise, or a "deferred" in your onSubmit handler. This would allow you to call Fine Uploader back (and say "go ahead, upload/submit this file and move on to the next") once your user has submitted the associated modal.
I have already created a qq.Promise "class" that implements the promise pattern. Fine Uploader observes a promise as a valid return value only for the onPasteReceived callback at this time. It might make sense to allow this as a valid return value for other callbacks, such as onSubmit. This would allow you to defer uploading/submittal of a file until the user has dealt with the associated modal. Setting the multiple option to "false" may or may not be a desirable option for you now, due to the way Fine Uploader functions in "single-file mode".
I think it may be valuable to allow promises to be returned in some callbacks, such as this one, to allow for user interaction. Could you please open up a feature request in the Github project? There, we can discuss a bit more and you can track progress on any associated changes I put into the library.
I'm trying to write a restartless add-on for Firefox Mobile that will insert content onto specific web pages. It all seems to work OK until I try disabling then re-enabling the add-on, at which point I get multiple responses to the page load event, and I can't figure out a way to sort them out.
Since Fennec uses the Electrolysis multi-process platform, I know that I need to split my code into chrome and content scripts. My bootstrap.js looks something like this (trimmed for clarity):
function startup(data, reason) {
mm = Cc["#mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager);
mm.loadFrameScript(getResourceURISpec('content.js'), true);
}
function shutdown(data, reason) {
let mm = Cc["#mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager);
mm.sendAsyncMessage("GeoMapEnh:Disable", {reason: reason});
}
function install(data, reason) {}
function uninstall(data, reason) {}
Basically, the bootstrap.js just launches a content script, and sends a message to tell it to clean up on shutdown. The content.js sets up an eventListener to watch for page loads, that looks a bit like this:
addMessageListener("GeoMapEnh:Disable", disableScript);
addEventListener("DOMContentLoaded", loadHandler, false );
function loadHandler(e) {
LOG("Page loaded");
// Do something cool with the web page.
}
function disableScript(aMessage) {
if (aMessage.name != "GeoMapEnh:Disable") {
return;
}
LOG("Disabling content script: " + aMessage.json.reason);
try {
removeEventListener("DOMContentLoaded", loadHandler, false );
removeMessageListener("GeoMapEnh:Disable", disableScript);
} catch(e) {
LOG("Remove failed: " + e);
}
}
function LOG(msg) {
dump(msg + "\n");
var consoleService = Cc["#mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
consoleService.logStringMessage(msg);
}
When I first run the extension, everything works fine. An instance of content.js is executed for each browser tab (and any new tabs I open) and my eventListener detects the page loads it is supposed to via the DOMContentLoaded event. When I disable the extension, everything still seems OK: page loads stop being detected.
When I re-enable the extension, it all goes wrong. I still get an instance of content.js executing for each open tab, but now, if I open new tabs, DOMContentLoaded triggers mutiple eventListeners and I can't distinguish which one should handle the event. Worse yet, some of the eventListeners are active, but do not give debug info via my LOG function, and do not all of them get removed if I disable the extension a second time.
I do want to watch all browser tabs, including any new ones, but I only want my extension to insert content on the page that triggers it, and only once per page load. I've tried the following without success:
Calling e.stopPropagation() to stop the event being passed to other listeners. No effect.
Calling e.preventDefault() and testing e.defaultPrevented to see if the event has already been handled. It never has.
Testing if (this === content.document) to see if the eventListener has been triggered by its own page content. Doesn't work, as I get multiple "true" responses.
Making the eventListener capturing. No effect.
Using the load event rather than DOMContentLoaded.
I can't set a shared variable to say the event has been handled as under Electrolysis, the different eventListeners will be executing in different contexts. Also, I wouldn't be able to distinguish between multiple tabs loading the same page and one page load being detected multiple times. I could do this via IPC message passing back to the chrome bootstrap script, but I then I wouldn't know how to address the response back to the correct browser tab.
Any ideas? Is this a Firefox bug, or am I doing something silly in my code? I am using Fennec Desktop v4 for development, and will target Fennec Android v6 for production.
I am trying to write a mobile firefox plugin that executes a piece of javascript code automatically every time a page loads. I had written some code for an earlier version of Fennec, but with the multi-processing system in the newer Fennec version (https://wiki.mozilla.org/Mobile/Fennec/Extensions/Electrolysis/), this code had to be ported. I based myself on a tutorial from http://people.mozilla.com/~mfinkle/tutorials/ to get a version working that executes a piece of code whenever an option is selected in the browser menu. This solution consists of two parts, namely overlay.js (for the main (application) process) and content.js (for the child (content) process). Overlay.js is loaded in overlay.xul, while content.js is loaded for new tabs via the following code in overlay.js:
window.messageManager.loadFrameScript("chrome://coin/content/content.js", true);
The code in overlay.js sends a message to content.js whenever the option in the browser menu is clicked, and the required code is then correctly executed (some script tags are simply added to the head of the page). However, I don't know how to execute code automatically on a page load. I tried the following in content.js:
function addCoin(aMessage) { ... }
// this executes the desired code every time an option is clicked in the browser menu
addMessageListener("coin:addCoin", addCoin);
// this attempts to execute the code on every page load; i.e., after this script has
been loaded for the new tab
addCoin(null);
The last statement however has no effect. Then, I tried adding the following statement at the end:
sendAsyncMessage("coin:scriptLoaded", { });
This statement sends a message to the overlay.js script, which registers a listener for this message and in response simply sends the same message as when the option in the browser menu is clicked, i.e., "coin:addCoin". However, this didn't work either. Finally, I tried looking for certain events the overlay.js script could listen for (something like "tabOpened" or something), but couldn't find anything.
Does anyone have any ideas on how to automatically execute code on every page load?
Regards,
William
In your content.js script you can simply register an event listener for the "load" event, just like you would have in the old single process Firefox:
addEventListener("load", someFunc, true);
This will call "someFunc" any time a webpage loads in the tab.
Any global code in content.js is executed when the tab is initial created, not when a page loads. Use global code to set up event listeners or message listeners. The web content will still fire events you can catch in the content.js (child script).
This worked for me.
in content.js:
var addEventListener;
if (window.BrowserApp) { // We are running in Mobile Firefox
addEventListener = window.BrowserApp.deck.addEventListener;
} else {
var appcontent = document.getElementById("appcontent");
if (appcontent) {
addEventListener = appcontent.addEventListener;
}
}
if (addEventListener) {
var onDOMContentLoaded = function() { /* Code here */ };
addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
var onLoad = function() { /* Code here */ };
addEventListener("load", onLoad, true);
// etc ...
}