This question already has answers here:
How do I modify the URL without reloading the page?
(20 answers)
Closed 9 years ago.
I would like to know how to change the url without redirecting like on this website http://dekho.com.pk/ads-in-lahore
when we click on tabs the url changes but the page dosent reload completely. There are other questions on stackoverflow indicating that it is not possible but i would like to know how the above mentioned website have implemented it.
Thanks
use pushState:
window.history.pushState(data, title, url);
LE: since modern browsers changed behaviour, use replaceState instead:
window.history.replaceState(data, title, url);
If you want to know exactly what they using, it's Backbone.js (see lines 4574 and 4981). It's all mixed up in there with the jQuery source, but these are the relevant lines of the annotated Backbone.Router source documentation page:
The support checks:
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
The route function:
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
Choosing between hash and push states:
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
Backbone.$(window).bind('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
Backbone.$(window).bind('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
More on what they're up to:
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
this.location.replace(this.root + this.location.search + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, this.root + this.fragment);
}
if (!this.options.silent) return this.loadUrl();
And the coup 'd grace:
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
}
You should read the annotated Backbone.js link I provided at the top. Very informative.
Related
I am using Tampermonkey to save time on frequent tasks. The goal is to get content of an element on www.example1.com, navigate to another page, and do stuff there. The starting page is www.example1.com as seen from match. This is the code I am using:
//#match http://example1.com
var item = document.getElementById("myId").textContent;
window.open("http://example2.com","_self");
setTimeOut(function(
//perform clicks on this page
){},3000);
None of the code after changing URLs ever gets executed. Why, and what is the workaround?
Allow the userscript on both urls and use GM_setValue/GM_getValue to organize the communication.
//#match http://example1.com
//#match http://example2.com
//#grant GM_getValue
//#grant GM_setValue
if (location.href.indexOf('http://example1.com') == 0) {
GM_setValue('id', Date.now() + '\n' + document.getElementById("myId").textContent);
window.open("http://example2.com","_self");
} else if (location.href.indexOf('http://example2.com') == 0) {
var ID = GM_getValue('id', '');
if (ID && Date.now() - ID.split('\n')[0] < 10*1000) {
ID = ID.split('\n')[1];
.............. use the ID
}
}
This is a simplified example. In the real code you may want to use location.host or location.origin or match location.href with regexp depending on what the real urls are.
To pass complex objects serialize them:
GM_setValue('test', JSON.stringify({a:1, b:2, c:"test"}));
try { var obj = JSON.parse(GM_getValue('test')); }
catch(e) { console.error(e) }
I'm developing an Add-on SDK extension for Firefox. I find that I need to be able to launch a download as if the user requested it, that is, either showing the normal file save dialog or saving the file to wherever the user prefers, as it could be configured under preferences > content.
Every single post or documentation regarding downloads seem to only take in consideration the scenario where I know where to download the file, but that is not what I need in this case. In this case, it needs to be as if the user started the download.
How can this be accomplished, preferably via the methods of the SDK?
Well, you could just initiate an actual save.
Initiating a save link from your code:
In the context menu the oncommand value is gContextMenu.saveLink();. saveLink() is defined in: chrome://browser/content/nsContextMenu.js. It does some housekeeping and then calls saveHelper() which is defined in the same file. You could just call saveHelper() with appropriate arguments. It is included in panels from chrome://browser/content/web-panels.xul with:
<script type="application/javascript"
src="chrome://browser/content/nsContextMenu.js"/>
Then the gContextMenu variable declared in chrome://browser/content/browser.js as null is assigned:
gContextMenu = new nsContextMenu(this, event.shiftKey);
in the onpopupshowing event handler for context menus. It is returned to:
'gContextMenu = null;'
in the onpopuphiding event handler.
If you want to use it in your own code you can do:
let urlToSave = "http://stackoverflow.com/questions/26694442";
let linkText = "Some Link text";
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Overlay and bootstrap:
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
//*/
/* Add-on SDK:
var {Cc, Ci, Cr} = require("chrome");
//*/
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
//Create an object in which we attach nsContextMenu.js.
// It needs some support properties/functions which
// nsContextMenu.js assumes are part of its context.
let contextMenuObj = {
makeURI: function (aURL, aOriginCharset, aBaseURI) {
var ioService = Cc["#mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
return ioService.newURI(aURL, aOriginCharset, aBaseURI);
},
gPrefService: Cc["#mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.QueryInterface(Ci.nsIPrefBranch),
Cc: Cc,
Ci: Ci,
Cr: Cr
};
Cc["#mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/nsContextMenu.js"
,contextMenuObj);
//Re-define the initMenu function, as there is not a desire to actually
// initialize a menu.
contextMenuObj.nsContextMenu.prototype.initMenu = function() { };
let myContextMenu = new contextMenuObj.nsContextMenu();
//Save the specified URL
myContextMenu.saveHelper(urlToSave,linkText,null,true,window.content.document);
Alternative to using loadSubScript to load nsContextMenu.js:
My preference is to use loadSubScript to load the saveHelper code from nsContextMenu.js. This keeps the code up to date with any changes which are made in future Firefox releases. However, it introduces the dependency that you are using a function from a non-official API. Thus, it might change in some way in future Firefox release and require changes in your add-on. The alternative is to duplicate the saveHelper() code in your extension. It is defined as the following:
// Helper function to wait for appropriate MIME-type headers and
// then prompt the user with a file picker
saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) {
// canonical def in nsURILoader.h
const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
// an object to proxy the data through to
// nsIExternalHelperAppService.doContent, which will wait for the
// appropriate MIME-type headers and then prompt the user with a
// file picker
function saveAsListener() {}
saveAsListener.prototype = {
extListener: null,
onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
// if the timer fired, the error status will have been caused by that,
// and we'll be restarting in onStopRequest, so no reason to notify
// the user
if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
return;
timer.cancel();
// some other error occured; notify the user...
if (!Components.isSuccessCode(aRequest.status)) {
try {
const sbs = Cc["#mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
const bundle = sbs.createBundle(
"chrome://mozapps/locale/downloads/downloads.properties");
const title = bundle.GetStringFromName("downloadErrorAlertTitle");
const msg = bundle.GetStringFromName("downloadErrorGeneric");
const promptSvc = Cc["#mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
promptSvc.alert(doc.defaultView, title, msg);
} catch (ex) {}
return;
}
var extHelperAppSvc =
Cc["#mozilla.org/uriloader/external-helper-app-service;1"].
getService(Ci.nsIExternalHelperAppService);
var channel = aRequest.QueryInterface(Ci.nsIChannel);
this.extListener =
extHelperAppSvc.doContent(channel.contentType, aRequest,
doc.defaultView, true);
this.extListener.onStartRequest(aRequest, aContext);
},
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
aStatusCode) {
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
// do it the old fashioned way, which will pick the best filename
// it can without waiting.
saveURL(linkURL, linkText, dialogTitle, bypassCache, false,
doc.documentURIObject, doc);
}
if (this.extListener)
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
},
onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
aInputStream,
aOffset, aCount) {
this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
aOffset, aCount);
}
}
function callbacks() {}
callbacks.prototype = {
getInterface: function sLA_callbacks_getInterface(aIID) {
if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
// If the channel demands authentication prompt, we must cancel it
// because the save-as-timer would expire and cancel the channel
// before we get credentials from user. Both authentication dialog
// and save as dialog would appear on the screen as we fall back to
// the old fashioned way after the timeout.
timer.cancel();
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
// if it we don't have the headers after a short time, the user
// won't have received any feedback from their click. that's bad. so
// we give up waiting for the filename.
function timerCallback() {}
timerCallback.prototype = {
notify: function sLA_timer_notify(aTimer) {
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
return;
}
}
// set up a channel to do the saving
var ioService = Cc["#mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var channel = ioService.newChannelFromURI(makeURI(linkURL));
if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
channel.setPrivate(docIsPrivate);
}
channel.notificationCallbacks = new callbacks();
let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
if (bypassCache)
flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
if (channel instanceof Ci.nsICachingChannel)
flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
channel.loadFlags |= flags;
if (channel instanceof Ci.nsIHttpChannel) {
channel.referrer = doc.documentURIObject;
if (channel instanceof Ci.nsIHttpChannelInternal)
channel.forceAllowThirdPartyCookie = true;
}
// fallback to the old way if we don't see the headers quickly
var timeToWait =
gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
var timer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(new timerCallback(), timeToWait,
timer.TYPE_ONE_SHOT);
// kick off the channel with our proxy object as the listener
channel.asyncOpen(new saveAsListener(), null);
}
Like #canuckistani said, use the Downloads.jsm
let { Downloads } = require("resource://gre/modules/Downloads.jsm");
let { OS } = require("resource://gre/modules/osfile.jsm")
let { Task } = require("resource://gre/modules/Task.jsm");
Task.spawn(function () {
yield Downloads.fetch("http://www.mozilla.org/",
OS.Path.join(OS.Constants.Path.tmpDir,
"example-download.html"));
console.log("example-download.html has been downloaded.");
}).then(null, Components.utils.reportError);
I'm trying to use SoundJS to load sounds dynamically.
If I register a sound that has been already registered with createjs.Sound.registerSound, I don't get the callback function to load (called by the "fileload" event listener).
createjs.Sound.addEventListener("fileload", createjs.proxy(this.loadHandler, this));
createjs.Sound.registerSound(url, "song");
According to a post here and SoundJS documentation, I can check if the sound has been already loaded with loadComplete(url).
But this:
createjs.Sound.loadComplete("audio/file.mp3")
...returns:
Uncaught TypeError: Cannot read property 'mp3' of null.
The problem seems to be that the following function is returning null, because activePlugin is null. Why that happens when audio is playing correctly (I'm testing this on Chrome on a Mac)? Shouldn't it be using defaults WebAudioPlugin or HTMLAudioPlugin?
from soundjs-0.5.2.combined.js:
s.getCapabilities = function () {
if (s.activePlugin == null) {
return null;
}
return s.activePlugin._capabilities;
};
UPDATE
Here's a temporary workaround, ugly, but works.
I solved by doing two things:
1) before using for createjs.Sound.loadComplete(url), I first check if createjs.Sound.activePlugin is true.
2) for handling audio files that weren't loaded yet, I added a conditional inside the
SoundJS.loadComplete() function to check if s._preloadHash[src] === undefined, and return false if so (that means that URLs that aren't registered urls will return false, as if the file hasn't been loaded):
s.loadComplete = function (src) {
var details = s._parsePath(src);
if (details) {
src = s._getSrcById(details.src).src;
} else {
src = s._getSrcById(src).src;
}
// song has not been registered. return false
if (s._preloadHash[src] === undefined)
return false;
return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all
};
...and in another JavaScript file:
loadSong: function(url){
currentSongURL = url;
// if Sound.activePlugin returns false, load the song.
if (!createjs.Sound.activePlugin){
createjs.Sound.addEventListener("fileload", createjs.proxy(this.loadHandler, this));
createjs.Sound.registerSound(url, url); // url, id
return;
}
// if song is already loaded, resolve deferred.
if(createjs.Sound.loadComplete(url)) {
songDeferred.resolve();
}else{
createjs.Sound.addEventListener("fileload", createjs.proxy(this.loadHandler, this));
createjs.Sound.registerSound(url, url); // url, id
}
},
I'm studying how to extend SoundJS.loadComplete() without messing the SoundJS class, and how to avoid the repeated code on my loadSong() function above. It looks very messy indeed but that's what I got for now to make it work the way I intend to. Any help on that would be deeply appreciated.
I'm trying to redirect a tab to a new page when the URL matches my pattern before it's done loading. The method I came up with does the redirection after a good part of the page is done loading yet.
var tabs = require("sdk/tabs");
var tab_utils = require("sdk/tabs/utils");
function logShow(tab) {
console.log(tab.url + " is loaded; " + pattern.test(tab.url));
if (pattern.test(tab.url)) {
var lowLevelTab = viewFor(tab);
console.log(tab_utils.setTabURL (lowLevelTab, newURL(tab.url)));
// also simply replacing this bit with
// tab.url = "foo" doesn't speed things up
}
}
tabs.on('load', logShow);
Is there a good way of calling setTabURL (...) earlier?
I finally found the best way to do it:
function listener(event) {
var channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
var url = event.subject.URI.spec;
// Here you should evaluate the url and decide if make a redirect or not.
if (pattern.test(url)) {
// If you want to redirect to another url,
// you have to abort current request, see: [1] (links below)
channel.cancel(Cr.NS_BINDING_ABORTED);
// Set the current gbrowser object (since
// the user may have several windows/tabs)
var goodies = loadContextGoodies(channel);
var domWin = goodies.aDOMWindow; // method suggested by
var gBrowser = goodies.gBrowser; // Noitidart [2] (links below)
var browser = goodies.browser; // for reference see comment below
var htmlWindow = goodies.contentWindow;
// and load the fixed URI
browser.loadURI(newUrl(url));
} else {
// do nothing, let Firefox keep going on the normal flow
}
}
exports.main = function() {
events.on("http-on-modify-request", listener);
}
credit where credit is due: answer by matagus (on question asked by Andrew)
[1]: Link: Intercepting Page Loads
[2]: Noitidart: 'from topics: How can I change the User Agent in just one tab of Firefox? and Is it possible to know the target DOMWindow for an HTTPRequest?'
Never used sdk/tabs before, but you could load your content hidden.
Once your page has loaded your logShow function will run.
Then build into this function some "reveal body" functionality.
I want to intercept all route changes with Sammy to first check if there is a pending action. I have done this using the sammy.before API and I return false to cancel the route. This keeps the user on the 'page' but it still changes the hash in the browsers address bar and adds the route to the browsers' history. If I cancel the route, I dont want it in the address bar nor history, but instead I expect the address to stay the same.
Currently, to get around this I can either call window.history.back (yuk) to go back to the original spot in the history or sammy.redirect. Both of which are less than ideal.
Is there a way to make sammy truly cancel the route so it stays on the current route/page, leaves the address bar as is, and does not add to the history?
If not, is there another routing library that will do this?
sammy.before(/.*/, function () {
// Can cancel the route if this returns false
var response = routeMediator.canLeave();
if (!isRedirecting && !response.val) {
isRedirecting = true;
// Keep hash url the same in address bar
window.history.back();
//this.redirect('#/SpecificPreviousPage');
}
else {
isRedirecting = false;
}
return response.val;
});
In case someone else hits this, here is where I ended up. I decided to use the context.setLocation feature of sammy to handle resetting the route.
sammy.before(/.*/, function () {
// Can cancel the route if this returns false
var
context = this,
response = routeMediator.canLeave();
if (!isRedirecting && !response.val) {
isRedirecting = true;
toastr.warning(response.message); // toastr displays the message
// Keep hash url the same in address bar
context.app.setLocation(currentHash);
}
else {
isRedirecting = false;
currentHash = context.app.getLocation();
}
return response.val;
});
When using the code provided within the question and answer you have to notice that the route you cancelled will also be blocked for all future calls, routeMediator.canLeave will not be evaluated again. Calling a route twice and cancelling it depending on current state is not possible with this.
I could produce the same results as John Papa did when he used SammyJS on the SPA/Knockout course.
I used Crossroads JS as the router, which relies on Hasher JS to listen to URL changes "emitted" by the browser.
Code sample is:
hasher.changed.add(function(hash, oldHash) {
if (pageViewModel.isDirty()){
console.log('trying to leave from ' + oldHash + ' to ' + hash);
hasher.changed.active = false;
hasher.setHash(oldHash);
hasher.changed.active = true;
alert('cannot leave. dirty.');
}
else {
crossroads.parse(hash);
console.log('hash changed from ' + oldHash + ' to ' + hash);
}
});
After revisiting an older project and having a similar situation, I wanted to share another approach, just in case someone else is directed here.
What was needed was essentially a modern "auth guard" pattern for intercepting pages and redirecting based on credentials.
What worked well was using Sammy.around(callback) as defined here:
Sammy.js docs: Sammy.Application around(callback)
Then, simply do the following...
(function ($) {
var app = Sammy("body");
app.around(checkLoggedIn);
function canAccess(hash) {
/* access logic goes here */
return true;
}
// Authentication Guard
function authGuard(callback) {
var context = this;
var currentHash = app.getLocation();
if (!canAccess(currentHash)) {
// redirect
context.redirect("#/login");
}
else {
// execute the route path
callback();
}
};
})(jQuery);