I have problem with caching partials in AngularJS.
In my HTML page I have:
<body>
<div ng-view></div>
<body>
where my partials are loaded.
When I change HTML code in my partial, browser still load old data.
Is there any workaround?
For Development you can also deactivate the browser cache - In Chrome Dev Tools on the bottom right click on the gear and tick the option
Disable cache (while DevTools is open)
Update: In Firefox there is the same option in Debugger -> Settings -> Advanced Section (checked for Version 33)
Update 2: Although this option appears in Firefox some report it doesn't work. I suggest using firebug and following hadaytullah answer.
Building on #Valentyn's answer a bit, here's one way to always automatically clear the cache whenever the ng-view content changes:
myApp.run(function($rootScope, $templateCache) {
$rootScope.$on('$viewContentLoaded', function() {
$templateCache.removeAll();
});
});
As mentioned in the other answers, here and here, the cache can be cleared by using:
$templateCache.removeAll();
However as suggested by gatoatigrado in the comment, this only appears to work if the html template was served without any cache headers.
So this works for me:
In angular:
app.run(['$templateCache', function ( $templateCache ) {
$templateCache.removeAll(); }]);
You may be adding cache headers in a variety of ways but here are a couple of solutions that work for me.
If using IIS, add this to your web.config:
<location path="scripts/app/views">
<system.webServer>
<staticContent>
<clientCache cacheControlMode="DisableCache" />
</staticContent>
</system.webServer>
</location>
If using Nginx, you can add this to your config:
location ^~ /scripts/app/views/ {
expires -1;
}
Edit
I just realised that the question mentioned dev machine but hopefully this may still help somebody...
If you are talking about cache that is been used for caching of templates without reloading whole page, then you can empty it by something like:
.controller('mainCtrl', function($scope, $templateCache) {
$scope.clearCache = function() {
$templateCache.removeAll();
}
});
And in markup:
<button ng-click='clearCache()'>Clear cache</button>
And press this button to clear cache.
Solution For Firefox (33.1.1) using Firebug (22.0.6)
Tools > Web-Tools > Firebug > Open Firebug.
In the Firebug views go to the "Net" view.
A drop down menu symbol will appear next to "Net" (title of the view).
Select "Disable Browser Cache" from the drop down menu.
This snippet helped me in getting rid of template caching
app.run(function($rootScope, $templateCache) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (typeof(current) !== 'undefined'){
$templateCache.remove(current.templateUrl);
}
});
});
The details of following snippet can be found on this link:
http://oncodesign.io/2014/02/19/safely-prevent-template-caching-in-angularjs/
I'm posting this just to cover all possibilities since neither of the other solutions worked for me (they threw errors due angular-bootstrap template dependencies, among others).
While you are developing/debugging a specific template, you can ensure it always refreshes by included a timestamp in the path, like this:
$modal.open({
// TODO: Only while dev/debug. Remove later.
templateUrl: 'core/admin/organizations/modal-selector/modal-selector.html?nd=' + Date.now(),
controller : function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
}
});
Note the final ?nd=' + Date.now() in the templateUrl variable.
As others have said, defeating caching completely for dev purposes can be done easily without changing code: use a browser setting or a plugin. Outside of dev, to defeat Angular template caching of route-based templates, remove the template URL from the cache during $routeChangeStart (or $stateChangeStart, for UI Router) as Shayan showed. However, that does NOT affect the caching of templates loaded by ng-include, because those templates are not loaded through the router.
I wanted to be able to hotfix any template, including those loaded by ng-include, in production and have users receive the hotfix in their browser quickly, without having to reload the entire page. I'm also not concerned about defeating HTTP caching for templates. The solution is to intercept every HTTP request that the app makes, ignore those that are not for my app's .html templates, then add a param to the template's URL that changes every minute. Note that the path-checking is specific to the path of your app's templates. To get a different interval, change the math for the param, or remove the % completely to get no caching.
// this defeats Angular's $templateCache on a 1-minute interval
// as a side-effect it also defeats HTTP (browser) caching
angular.module('myApp').config(function($httpProvider, ...) {
$httpProvider.interceptors.push(function() {
return {
'request': function(config) {
config.url = getTimeVersionedUrl(config.url);
return config;
}
};
});
function getTimeVersionedUrl(url) {
// only do for html templates of this app
// NOTE: the path to test for is app dependent!
if (!url || url.indexOf('a/app/') < 0 || url.indexOf('.html') < 0) return url;
// create a URL param that changes every minute
// and add it intelligently to the template's previous url
var param = 'v=' + ~~(Date.now() / 60000) % 10000; // 4 unique digits every minute
if (url.indexOf('?') > 0) {
if (url.indexOf('v=') > 0) return url.replace(/v=[0-9](4)/, param);
return url + '&' + param;
}
return url + '?' + param;
}
If you are using UI router then you can use a decorator and update $templateFactory service and append a query string parameter to templateUrl, and the browser will always load the new template from the server.
function configureTemplateFactory($provide) {
// Set a suffix outside the decorator function
var cacheBust = Date.now().toString();
function templateFactoryDecorator($delegate) {
var fromUrl = angular.bind($delegate, $delegate.fromUrl);
$delegate.fromUrl = function (url, params) {
if (url !== null && angular.isDefined(url) && angular.isString(url)) {
url += (url.indexOf("?") === -1 ? "?" : "&");
url += "v=" + cacheBust;
}
return fromUrl(url, params);
};
return $delegate;
}
$provide.decorator('$templateFactory', ['$delegate', templateFactoryDecorator]);
}
app.config(['$provide', configureTemplateFactory]);
I am sure you can achieve the same result by decorating the "when" method in $routeProvider.
I found that the HTTP interceptor method works pretty nicely, and allows additional flexibility & control. Additionally, you can cache-bust for each production release by using a release hash as the buster variable.
Here is what the dev cachebusting method looks like using Date.
app.factory('cachebustInjector', function(conf) {
var cachebustInjector = {
request: function(config) {
// new timestamp will be appended to each new partial .html request to prevent caching in a dev environment
var buster = new Date().getTime();
if (config.url.indexOf('static/angular_templates') > -1) {
config.url += ['?v=', buster].join('');
}
return config;
}
};
return cachebustInjector;
});
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('cachebustInjector');
}]);
Here is another option in Chrome.
Hit F12 to open developer tools. Then Resources > Cache Storage > Refresh Caches.
I like this option because I don't have to disable cache as in other answers.
There is no solution to prevent browser/proxy caching since you cannot have the control on it.
The other way to force fresh content to your users it to rename the HTML file! Exactly like https://www.npmjs.com/package/grunt-filerev does for assets.
Related
Got an issue with safari loading old youtube videos when back button is clicked. I have tried adding onunload="" (mentioned here Preventing cache on back-button in Safari 5) to the body tag but it doesn't work in this case.
Is there any way to prevent safari loading from cache on a certain page?
Your problem is caused by back-forward cache. It is supposed to save complete state of page when user navigates away. When user navigates back with back button page can be loaded from cache very quickly. This is different from normal cache which only caches HTML code.
When page is loaded for bfcache onload event wont be triggered. Instead you can check the persisted property of the onpageshow event. It is set to false on initial page load. When page is loaded from bfcache it is set to true.
Kludgish solution is to force a reload when page is loaded from bfcache.
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload()
}
};
If you are using jQuery then do:
$(window).bind("pageshow", function(event) {
if (event.originalEvent.persisted) {
window.location.reload()
}
});
All of those answer are a bit of the hack. In modern browsers (safari) only on onpageshow solution work,
window.onpageshow = function (event) {
if (event.persisted) {
window.location.reload();
}
};
but on slow devices sometimes you will see for a split second previous cached view before it will be reloaded. Proper way to deal with this problem is to set properly Cache-Control on the server response to one bellow
'Cache-Control', 'no-cache, max-age=0, must-revalidate, no-store'
Yes the Safari browser does not handle back/foreward button cache the same like Firefox and Chrome does. Specially iframes like vimeo or youtube videos are cached hardly although there is a new iframe.src.
I found three ways to handle this. Choose the best for your case.
Solutions tested on Firefox 53 and Safari 10.1
1. Detect if user is using the back/foreward button, then reload whole page or reload only the cached iframes by replacing the src
if (!!window.performance && window.performance.navigation.type === 2) {
// value 2 means "The page was accessed by navigating into the history"
console.log('Reloading');
//window.location.reload(); // reload whole page
$('iframe').attr('src', function (i, val) { return val; }); // reload only iframes
}
2. reload whole page if page is cached
window.onpageshow = function (event) {
if (event.persisted) {
window.location.reload();
}
};
3. remove the page from history so users can't visit the page again by back/forward buttons
$(function () {
//replace() does not keep the originating page in the session history,
document.location.replace("/Exercises#nocache"); // clear the last entry in the history and redirect to new url
});
You can use an anchor, and watch the value of the document's location href;
Start off with http://acme.co/, append something to the location, like '#b';
So, now your URL is http://acme.co/#b, when a person hits the back button, it goes back to http://acme.co, and the interval check function sees the lack of the hash tag we set, clears the interval, and loads the referring URL with a time-stamp appended to it.
There are some side-effects, but I'll leave you to figure those out ;)
<script>
document.location.hash = "#b";
var referrer = document.referrer;
// setup an interval to watch for the removal of the hash tag
var hashcheck = setInterval(function(){
if(document.location.hash!="#b") {
// clear the interval
clearInterval(hashCheck);
var ticks = new Date().getTime();
// load the referring page with a timestamp at the end to avoid caching
document.location.href.replace(referrer+'?'+ticks);
}
},100);
</script>
This is untested but it should work with minimal tweaking.
The behavior is related to Safari's Back/Forward cache. You can learn about it on the relevant Apple documentation: http://web.archive.org/web/20070612072521/http://developer.apple.com/internet/safari/faq.html#anchor5
Apple's own fix suggestion is to add an empty iframe on your page:
<iframe style="height:0px;width:0px;visibility:hidden" src="about:blank">
this frame prevents back forward cache
</iframe>
(The previous accepted answer seems valid too, just wanted to chip in documentation and another potential fix)
I had the same issue with using 3 different anchor links to the next page. When coming back from the next page and choosing a different anchor the link did not change.
so I had
House 1
View House 2
View House 3
Changed to
House 1
View House 2
View House 3
Also used for safety:
// Javascript
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload()
}
};
// JQuery
$(window).bind("pageshow", function(event) {
if (event.originalEvent.persisted) {
window.location.reload()
}
});
None of the solutions found online to unload, reload and reload(true) singularily didn't work. Hope this helps someone with the same situation.
First of all insert field in your code:
<input id="reloadValue" type="hidden" name="reloadValue" value="" />
then run jQuery:
jQuery(document).ready(function()
{
var d = new Date();
d = d.getTime();
if (jQuery('#reloadValue').val().length == 0)
{
jQuery('#reloadValue').val(d);
jQuery('body').show();
}
else
{
jQuery('#reloadValue').val('');
location.reload();
}
});
There are many ways to disable the bfcache. The easiest one is to set an 'unload' handler. I think it was a huge mistake to make 'unload' and 'beforeunload' handlers disable the bfcache, but that's what they did (if you want to have one of those handlers and still make the bfcache work, you can remove the beforeunload handler inside the beforeunload handler).
window.addEventListener('unload', function() {})
Read more here:
https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/1.5/Using_Firefox_1.5_caching
I am new to UI coding and started using react-admin for putting some simple pages. Everything went well and we are able to host pages correctly. But we have noticed random issues where the background image is filling up the entire screen or sometimes the whole page gets reduced to the hamburger menu. I have disabled the registerServiceWorker to stop having my pages in cache. Not sure if this is causing the weird UI behavior.
I don't know why you get those issues, the description is way too generic and it seems you don't have any idea what the problem can be, probably due to being new to the area. Either way the kind of problem you appear to have is probably related to CSS which is a way give style to your page. But React Admin doesn't use CSS directly, you can use it that way, but for more dynamic way to style the page the Material-ui library uses a thing called JSS to apply the styles.
There are many libraries that are being used together in order to produce React Admin, you should have an understanding of the most important ones in order to do something fancy. My advice to you since you are new, and you pretend to use React Admin, first use what React Admin offers and when you feel comfortable using that components and have a general grasp how the framework works, after that start implementing your own components that don't have a direct relation to React Admin but use some of the same libraries of React Admin.
Also check if you are creating a React Admin app using the <Admin> component or are embedding React Admin in another app since the second is more probable to produce bugs.
After some debugging, I think i figured out the cause of this issue. I had a custom button to duplicate a row (basically post a create and route to edit page on the new id). For some reason, the rendering of that button seems to have caused this issue inconsistently. The actual button works fine but causes this inconsistent behavior. Below is the code for that button. Is there any issue with the below?:
export default class DuplicateButton extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = ({ redirect: false });
var redirectPath = '';
}
handleClick = (props) => {
var
{
push, record, resourceName
} = this.props;
let tempRecord = record;
var result = '';
console.log(this.props);
var p = restDataProvider(CREATE, this.props.resource + "/" + tempRecord.id, { data: tempRecord }).then(resp => {
result = resp.data;
let routePath = '/' + this.props.resource + '/' + result.id;
console.log(routePath);
this.redirectPath = routePath;
this.setState({ redirect: true });
return result;
});
}
render() {
if (this.state.redirect) {
console.log('Redirect to Edit page');
return <Redirect push to={this.redirectPath} />;
}
return <Button variant="flat" color="primary" label="Duplicate Entry" onClick={this.handleClick}><DuplicateIcon /></Button>;
}
}
I'm porting a Chrome extension to a Firefox extension and due to the nature of the website that it runs on, I need to monitor the pushState.
Chrome Extensions has a handy way to handle this: chrome.webNavigation.onHistoryStateUpdated. The way that I use it in the Chrome extension is as follows:
chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) {
var tabUrl = details.url;
if (isTabUrlValid(tabUrl)) {
$.get(tabUrl, function(data) {
var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
videoUrl = validateUrl(videoUrl);
videoUrl5k = make5kUrl(videoUrl);
});
}
});
I need to do the same thing for the Firefox Extension, but I haven't found any good answers. I've tried doing the answer mentioned here: How to get notified about changes of the history via history.pushState?
(function(history) {
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
var tabUrl = tabs.activeTab.url;
console.log("UPDATED TAB URL: " + tabUrl);
if (isTabUrlValid(tabUrl)) {
$.get(tabUrl, function(data) {
var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
videoUrl = validateUrl(videoUrl);
videoUrl5k = make5kUrl(videoUrl);
});
}
return pushState.apply(history, arguments);
};
})(window.history);
The problem is that when I do cfx run it complains that history/window is undefined and therefore never gets detected. I think this is due to it being within the SDK, but I don't know of a good workaround.
Any thoughts?
Edit: I looked at #willma's answer below and I don't think that would work for me. The issue is that the URL is updated via pushState and the DOM is not... Is there any good way replicate what I do in the chrome extension?
Edit: Here's the pageMod portion
pageMod.PageMod({
attachTo: 'top', // Don't attach to iFrames --> http://goo.gl/b6b1Iv
include: [URLs],
contentScriptFile: [data.url("jquery-2.1.1.min.js"),
data.url("csScript.js")],
onAttach: function(worker) {
worker.port.on('url', function(url) {
var videoUrl = validateUrl(url);
videoUrl5k = make5kUrl(videoUrl);
console.log("--5K URL--: " + videoUrl5k);
});
}
});
That history code needs to get injected into a tab using a content script. Right now your logic says when the history event occurs, check to see if the tab URL is valid.
In Firefox, the logic will be the other way around: when a tab is opened, check if its URL is valid, and if so, then attach a script to it that will monitor for the history event. To do so you'll need to use a Page Mod.
Edit: All the code
One key concept you're missing is the difference between a content script and a main/library script. The library scripts are stored in lib and have access to all the SDK modules, but don't have access to the DOM, window object… The content scripts are stored in data, are injected into a page using the PageMod or tabs modules, can access the dom and window objects, but have no access to any SDK modules. Content scripts are essentially like the page scripts you'd attach your standard HTML page (with <script></script>) with the caveats that they can't share variables other page scripts but they can communicate with the main scripts.
The only reason I bring this up is because your initial problem was trying to access the window object from a main script and the problem in your fiddle is that you're trying to access the tabs module inside a content script. It's worth reading the topmost link in this answer if this is still confusing.
main.js
const { PageMod } = require('sdk/page-mod');
var sendXHR = function(url) {
// Do something with the new URL
// See Request Module docs (below) for sending XHRs from main script.
}
const pageMod = PageMod({
attachTo: 'top',
include: '*',
onAttach: function(worker) {
worker.port.on('newURL', sendXHR);
}
});
content.js
var sendNewUrlToMain = function() {
self.port.emit('newURL', location.href);
}
var pushState = window.history.pushState;
window.history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
sendNewUrlToMain();
return pushState.apply(history, arguments);
}
window.addEventListener('hashchange', sendNewUrlToMain);
Here are the request module docs, for making XHRs.
NB: if you don't want to use the request module (the only reason being that you already have standard XHR code for your chrome extension and don't want to take the time to learn/rewrite that code), you can send a standard XHR from the content script, but in doing so, you risk allowing the user to close the tab and thus destroy the script before your XHR callbacks are executed.
I have recently asked where global stylesheets are for editing Cognos 10 styles (Here).
After some discussions with our team we would like to find the CGI or base imported file that Cognos uses to construct it's report viewer pages and dashboard widget holders.
The reason we want to do this is so that we can include all our custom style and javascript in one location. When/If we upgrade Cognos we can be sure of one point of failure with our reports. This would solve our problem of having to re-edit multiple stylesheets (and javascript).
I'm normally familiar with ASP.NET and not CGI-BIN. Is there something akin to a Master page where styles and basic imports are done for a Cognos page? Ideally editing this file would allow us to continue our customizations.
Can this be done? Or are we just insane? We understand the risks concerning upgrades, but are OK with the risks (unless someone can provide a good example of how this technique would not be replicated via version changes).
I think it's fairly common that BI professionals with more traditional web development backgrounds like me and you have no qualms with making changes to the global CSS files and bringing in more JS.
I've explained to you how I run JS in a report - I'd love to add jQuery to our global libraries, but I haven't drummed up enough support for it yet. I can help with the CSS portion though.
In 8.4.1, there's a ton of CSS files referenced by the report viewer. If I were you, I'd render a sample report with the default styling and use Firebug or similar to trace the CSS files being called. You'll find that server/cognos8/schemas/GlobalReportStyles.css is commonly referenced, with some help from server/cognos8/skins/corporate/viewer/QSRVCommon.css - there's also some other files in there that are imported.
I'd imagine you could grep -R '<link rel=\"stylesheet\" type=\"text/css\" href=\"../schemas/GlobalReportStyles.css\"> in the COGNOS directory to see where the file is being called, and either edit that file directly, or create a link to your own JS. Personally, I'd just backup the existing stylesheet and modify the one that is already there.
I'd imagine you could do something similar for the JS - find where it's being called in the template (using grep) and just create a new reference to the file you'd like to create. In my case, I'd do a backflip if I could get jQuery loaded into every report.
Just realized this is a year old. :-( Sorry, first time here. I'll leave it in case anyone is still interested in the topic.
Here is the documentation on customizing Cognos on several levels:
We used an alternative to modifying the system files. We have a shared component "report" containing an HTML object with our particular CSS overrides on it, and/or a link to a custom stylesheet. We then add this on each report with a "Layout Component Reference" from the toolbox. If we want a global change, just change the one item in the component report or custom stylesheet. This works very well for us.
I up-voted both the previous answers to this question. I'll admit I kind of forgot about this question till someone put some activity on it.
We ended up doing a combination of the above techniques. I was able to find the global stylesheets as suggested. What I ended up doing was copying out all the styles that were in that stylesheet and created a new sheet suffixed with *_SystemSytles.css*. I created a second sheet and suffixed it with *_Custom.css*. Then in the original sheet I placed two imports, first importing the system styles and then the custom styles.
For certain reports we have a custom object that is dropped on that brings in its own styles (and JavaScript). This utilizes a similar technique to the second question.
However, what I had to do for import the JavaScript for general use within the entire Cognos site was difficult.
In the core webcontent folder I created a js folder that contained the jQuery and our custom JavaScript files. Then in a series of JavaScript files I included code similar to the following:
/************************
JQUERY UTIL INCLUDE
************************/
function loadjscssfile(filename, filetype, id) {
if (filetype == "js") { //if filename is a external JavaScript file
var fileref = document.createElement('script')
fileref.setAttribute("type", "text/javascript")
fileref.setAttribute("src", filename)
if (id)
fileref.setAttribute("OurCompanyNameAsAnID", id)
}
else if (filetype == "css") { //if filename is an external CSS file
var fileref = document.createElement("link")
fileref.setAttribute("rel", "stylesheet")
fileref.setAttribute("type", "text/css")
fileref.setAttribute("href", filename)
}
if (typeof fileref != "undefined") {
var headTag = document.head || document.getElementsByTagName('head')[0];
headTag.appendChild(fileref);
}
}
function _PortalLoadJS() {
if (!window._PortalScriptsLoaded) {
var pathParams = [];
var path = location.href;
(function () {
var e,
r = /([^/]+)[/]?/g,
p = path;
while (e = r.exec(p)) {
pathParams.push(e[1]);
}
})();
var baseURL = location.protocol + '//';
for(var i = 1; i < pathParams.length; i++) {
if(pathParams[i] == 'cgi-bin')
break;
baseURL += pathParams[i] + '/';
}
loadjscssfile(baseURL + "js/jquery-1.6.1.min.js", "js");
loadjscssfile(baseURL + "js/Custom.js?pageType=COGNOS_CONNECTION", "js", "SumTotalUtil");
window._PortalScriptsLoaded = true;
}
}
if(!window.$CustomGlobal) {
window.$CustomGlobal= function(func) {
if (!window.$A) {
if (!window.__CustomExecStack) {
window.__CustomExecStack= new Array();
}
window.__CustomExecStack.push(func);
}
else
$A._executeCustomItem(func);
}
}
try {
// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if (document.readyState === "complete") {
// Handle it asynchronously to allow scripts the opportunity to delay ready
setTimeout(_PortalLoadJS, 10);
}
// Mozilla, Opera and webkit nightlies currently support this event
if (document.addEventListener) {
// Use the handy event callback
document.addEventListener("DOMContentLoaded", function() { _PortalLoadJS(); }, false);
// A fallback to window.onload, that will always work
window.addEventListener("load", _PortalLoadJS, false);
// If IE event model is used
} else if (document.attachEvent) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", function() { _PortalLoadJS(); });
// A fallback to window.onload, that will always work
window.attachEvent("onload", _PortalLoadJS);
}
}
catch (ex) { }
The $A item is an item that I create when the Custom.js file is loaded.
Here are the list of files that I've included this code (at the vary end of the JavaScript):
webcontent\icd\bux\js\bux\bux.core.js
webcontent\ps\portal\js\cc.js
webcontent\rv\CCognosViewer.js
webcontent\rv\GUtil.js
webcontent\rv\viewer.standalone.core.js
These files should cover the Cognos Connection, Report Viewer, and the Dashboards area. If any more are found please let me know and I can update this list.
When linking to the Custom.js file I put a query string on the external resource that the Custom.js file picks up: pageType=COGNOS_CONNECTION. This allows me to do specific load code for the Cognos Connection, Report Viewer, or the Dashboards.
Here is the code in the Custom.js class that inits the $A object:
function _CustomUtilInit() {
try {
if (!window.$j) {
window.setTimeout(_CustomUtilInit, 1);
return;
}
var jScriptTags = $j('SCRIPT[' + Analytics.SCRIPT_ATTR_NAME + '= ' + Analytics.SCRIPT_ATTR_VALUE + ']');
jScriptTags.each( function(i, scriptElem) {
var tag = $j(scriptElem);
if(tag.attr(Analytics.LOADED_SCRIPT_KEY))
return;
var scriptURL = new URI(tag.attr('src'));
var analyticsPageType = scriptURL.getQueryStringValue(Analytics.PAGE_TYPE_QUERY_KEY, Analytics.PageType.REPORT_VIEWER);
if(!window.$A) {
window.$A = new Analytics();
}
window.$A.init(analyticsPageType);
tag.attr(Analytics.LOADED_SCRIPT_KEY, 'true');
});
} catch (e) {
}
}
_CustomUtilInit();
Of course this expects that the jQuery libraries were included before the Custom.js files in each of the previously mentioned JavaScript files.
The URI class is something that I've found on the internet and tweaked for our use. If you have any questions regarding the custom JavaScript loading please leave a comment and I'll do my best to elaborate some more.
I have protocol (like http) with scheme managed with 3rd party App registered in Mac OS X.
I.e, x-someapp://someaction or something like that.
How can I open this URL with Google Chrome?
By default, Chrome starts searching in Google engine instead launching App and passing URL handling to it...
Safari launches some registered App. And it is right thing.
Firefox and Opera asks what to do... and I can launch App also.
But Chrome... Doesn't ask.
I even tried to write some HTML page with JavaScript inside to send XHttpRequest:
function _httpExecuteCallback()
{
if (httpRequestCallbackFunction != null) {
if (httpRequest.readyState == 4) {
if (httpRequest.status == 200) {
httpRequestCallbackFunction();
httpRequestCallbackFunction = null;
}
}
}
}
function _httpGet(url, callbackFunction)
{
httpRequest = false;
httpRequestCallbackFunction = callbackFunction;
httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = _httpExecuteCallback;
httpRequest.open('GET', url, true);
httpRequest.send(null);
}
_httpGet('x-someapp://test',function(){})
No results also...
The current accepted solution has a problem with Chrome for SSL https. Watching the console log, Chrome blocks the request because it thinks the custom url protocol is not secure:
[blocked] The page at reports blah blah ran insecure content from customproto//blah blah
Here is a solution (this took me a few days to research):
<input type='button' value='Test Custom Url' onclick='exec()'>
<script>
function submitRequest(buttonId) {
var d = (window.parent)?window.parent.document:window.document
if (d.getElementById(buttonId) == null || d.getElementById(buttonId) == undefined) return;
if (d.getElementById(buttonId).dispatchEvent) {
var e = d.createEvent("MouseEvents");
e.initEvent("click", true, true);
d.getElementById(buttonId).dispatchEvent(e);
}
else {
d.getElementById(buttonId).click();
}
}
function exec(){
var d = (window.parent)?window.parent.document:window.document
var f = d.getElementById('customUrlLink')
if (f ) {f.parentNode.removeChild(f);}
var a = d.createElement('a');
a.href = 'mycustomproto://arg1';
a.innerHTML = "Link"
a.setAttribute('id', 'customUrlLink');
a.setAttribute("style", "display:none; ");
d.body.appendChild(a);
submitRequest("customUrlLink");
}
</script>
This code will not work for IE. I've found using this technique IE limits the argument of the custom protocol to less than 1000 where as using the iFrame technique IE will allow 2083 chars.
The only way to overcome the url limit in javascript is chuck the data and call multiple times. If anyone wants to take a stab at that, please let me know how it goes. I would like to use it.
To handle long urls in the executing app, pass a token into the app and have it go get the data from a url GET.
So for right now I am using one function for Chrome/FF and another function for IE.
These links helped me develop this solution:
https://superuser.com/questions/655405/custom-protocol-handler-not-working-in-chrome-on-ssl-page
Simulating a click in jQuery/JavaScript on a link
(wish I had known this a few days ago....hope this helps someone)
==================================================
Update: (8hr later)
==================================================
Jake posted a great solution for chrome: https://superuser.com/questions/655405/custom-protocol-handler-not-working-in-chrome-on-ssl-page
This works in chrome only:
window.location.assign("customprotocol://");
It will fail in an iframe so this is working:
var w = (window.parent)?window.parent:window
w.location.assign(service + '://' + data)
==================================================
Update: (weeks later)
==================================================
All of the examples of opening the custom protocol, including my own, have a "://" in the url. And this is what is causing the SSL warnings.
Turns out the solution is to change "://" to ":"
so do this:
src="x-myproto:query" .....
and the SSL warnings will go away.
==================================================
Follow: (after months of production use)
==================================================
This has been working well for chorme. Detect the browser and if chrome do this:
var w = (window.parent)?window.parent:window
w.location.assign('myproto://xyzabcdefetc')
For IE and other browsers I do something slightly different.
Note that browsers do impose a limit on how much data you can put in custom url protocol. As long as your string is under 800 chars this seems to be the magic number for which works in all browsers.
It looks like it's Google's locationbar parsing which is getting in the way.
The browser, however, does seem to handle custom URL schemes properly. Try this in your locationbar:
javascript:document.location = 'myscheme://whatever'
Any link on your page that uses the custom scheme should also do the right thing.
I found the solution that works with Chrome.
I use the IFRAME-way.
Example (with JQuery):
$("body").append('<span id="__protoProxy"></span>');
function queryWord(aWord)
{
var protoProxy = document.getElementById('__protoProxy');
if (protoProxy)
{
var word = aWord.replace('"','\"');
protoProxy.innerHTML = '<div style="display:none;"><iframe src="x-myproto://query?' + word + '"></iframe></div>';
}
}
queryWord('hello');
Here's a solution that also includes a redirect to the App Store / Play Store if the user doesn't have the app. It uses a setTimeout for this. It also makes use of an iframe to support more browsers. So this works on Chrome, and any other mobile browser. We use this as my company, Branch. Just modify the two links below to correspond to your URI and App Store link.
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
window.onload = function() {
// Deep link to your app goes here
document.getElementById("l").src = "my_app://somepath";
setTimeout(function() {
// Link to the App Store should go here -- only fires if deep link fails
window.location = "https://itunes.apple.com/us/app/myapp/id123456789?ls=1&mt=8";
}, 500);
};
</script>
<iframe id="l" width="1" height="1" style="visibility:hidden"></iframe>
</body>
</html>
Again, this should work on any browser, thanks to the iframe.
If Chrome does not recognize the URL scheme, it defaults to a search.
This is what I see in Safari:
alt text http://img62.imageshack.us/img62/6792/clipboard02oh.jpg
and in Firefox:
alt text http://img138.imageshack.us/img138/9986/clipboard04xk.jpg
I believe the reason why Chrome defaults to search is that there are special google searches that use the colon.
E.g:
define: dictionary
filetype:pdf google chromium
This is one of the annoyances I have with Firefox, I have to jump to the "search box" rather than the address bar to execute these types of searches. Since Chrome does not have a separate search box like Firefox, IE and Safari have, this functionality is required.
Ajax requests won't get you around this.
Some weeks later ....
Looks like window.location.replace('myscheme://whatever') has full cross-browser support , works with chrome,firefox,safari,edge,opera see https://developer.mozilla.org/en-US/docs/Web/API/Location/replace