I have an angularjs based web application with some functionality deployed to users that I need to hide. I've added the code to hide it and successfully verified the controls are hidden when appropriate but there are still users who have the old version of the file and can perform the undesired activities. Is there a way I can control from the server the view file to refresh on the client? (The tester was able to clear their cache but it's a burden to the users in the field)
Thanks!
Scott
One way to handle this would be to version the files. For example, the following line in your index.html
<script src="abc.js" />
could be rewritten as
<script src="abc.js?v1" />
v1 is the current file version and should be changed for each deployment of your application when abc.js has changed.
Since index.html(the initial page) is obtained from the server, updations to abc.js will now be reflected on all your clients.
This would need to be automated in a huge application. You could use Grunt for this. You can refer the following answer on StackOverflow for automating this:
https://stackoverflow.com/a/20446748/802651
UPDATE
HTML views/templates are cached using $templateCache in AngularJS. Basically, when you request templates for the first time, browser requests the template from the server and puts it in the template cache. Any subsequent requests to the same template are served from the template cache.
If you do not want these to be cached, you could listen to the $routeChangeStart event inside app.run block to remove the specific templates.
app.run(function($rootScope, $templateCache) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (typeof(current) !== 'undefined'){
$templateCache.remove(current.templateUrl);
}
});
});
Reference: http://opensourcesoftwareandme.blogspot.in/2014/02/safely-prevent-template-caching-in-angularjs.html
Related
CefSharp: 1.25.0 (based on Chromium 25.0.1364.152)
Angular: 1.3.0-beta16
UIRouter: 0.2.10
I'm developing a stand-alone C# application that uses CefSharp Chromium + Angular + UIRouter as the stack upon which the GUI will be relying on.
I hit it off by trying to make the above stack load the sample-code provided here:
http://scotch.io/tutorials/javascript/angular-routing-using-ui-router
For the sake of elegance the HTML + Javascript-libs of the GUI, get cobundled in a single resource file inside the .Net executable of the application.
This resource is then passed programmatically during application-init to the Chromium control (by means of .LoadHtml) to be loaded directly into the browser, aka the HTML is not loaded from a separate .html file residing in the hard-drive or on a remote HTTP server. If the HTML gets loaded from the later ("standard") venues then everything works flawlessly.
I noticed that when loading the HTML directly as a string, as described above, the url of the resulting static web page (aka window.location) is set to 'about:blank'. It appears that angular has some sort of pet peeve with such a url, especially when it comes to using routing:
First of all, the invocation of:
history.pushState(null, "", url);
inside
self.url = function(url, replace) { ... }
throws an exception ala
Error: SecurityError: DOM Exception 18
Error: An attempt was made to break through the security policy of the user agent.
at Browser.self.url (about:blank:8004:21)
at about:blank:10049:24
at Scope.$eval (about:blank:11472:28)
at Scope.$digest (about:blank:11381:31)
at Scope.$apply (about:blank:11493:24)
at about:blank:6818:15
at Object.invoke (about:blank:7814:19)
at doBootstrap (about:blank:6817:16)
at bootstrap (about:blank:6827:14)
at angularInit (about:blank:6796:7)
the url that is passed to .pushState is:
about:blank#/home
which appears to be the result of concatenating 'about:blank' with the default state '/home'.
Secondly, even if the above problem is solved there appears to be a major issue inside:
$rootScope.$watch(function $locationWatch() { ... })
which causes the following error:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
the reason is that when 'window.location' is set to 'about:blank' then
$browser.url()
always returns
about:blank
while
$location.absUrl()
returns
about:blank#/home
causing $watch to fire non-stop.
Is there any proper way to handle this shortcoming of angular when its dealing with web pages loaded directly into the browser in the manner described here?
If there is no workaround for this issue then I'm afraid that I will have to resort to loading the HTML directly from a file in the hard drive, which apart from being slower (can't cache the string to memory for subsequent usages), it's also a noticable deviation from the goal of developing a stand-alone-exe. :(
Thanks in advance and I apologize if this issue has been addressed elsewhere.
By default Firefox allows loading of external files within html file that loaded from "file:///...". but Chrome does not. in CefSharp(Chrome) you can do it in this way:
// Allow angular routing and load external files
BrowserSettings setting = new BrowserSettings();
setting.FileAccessFromFileUrls = CefState.Enabled;
browser.BrowserSettings = setting;
this.Controls.Add(browser);
Most browsers don't allow to do AJAX on the file-system. But Chromium can be tweaked to do so:
browser = new ChromiumWebBrowser(path);
browser.BrowserSettings = new BrowserSettings();
browser.BrowserSettings.FileAccessFromFileUrlsAllowed = true;
I'm trying to get dojo to show Json data that comes from a remote web service. I need to be clear though - the web server hosting the html/dojo page I access isn't the same server as the one that's running the web service that returns the json data - the web service server just can't serve html pages reliably (don't ask!!).
As a test I move the page into the same web server as the web service and the below works. As soon as I move it back so that the html/dojo is served from Apache (//myhost.nodomain:82 say) and the web service sending the json is "{target:http://myhost.nodomain:8181}", then it stops working.
I've used FFox to look at the network & I see the web service being called ok, the json data is returned too & looks correct (I know it is from the previous test), but the fields are no longer set. I've tried this with DataGrid and the plain page below with the same effects.
Am I tripping up over something obvious???
Thanks
require([
"dojo/store/JsonRest",
"dojo/store/Memory",
"dojo/store/Cache",
"dojox/grid/DataGrid",
"dojo/data/ObjectStore",
"dojo/query",
"dojo/domReady!"
],
function(JsonRest, Memory, Cache, DataGrid, ObjectStore, query) {
var myStore, dataStore, grid;
myStore = JsonRest(
{
target: "http://localhost:8181/ws/job/definition/",
idProperty: "JOB_NAME"
}
);
myStore.query("JOB00001"
).then(function(results) {
var theJobDef = results[0];
dojo.byId("JOB_NAME").innerHTML = theJobDef.JOB_NAME;
dojo.byId("SCHEDULED_DAYS").innerHTML = theJobDef.SCHEDULED_DAYS;
});
}
);
Its true what Frans said about the cross domain restriction but dojo has this link to work around the problem.
require(["dojo/request/iframe"], function(iframe){
iframe("something.xml", {
handleAs: "json"
}).then(function(xmldoc){
// Do something with the XML document
}, function(err){
// Handle the error condition
});
// Progress events are not supported using the iframe provider
});
you can simply use this and the returned data can be inserted into a store and then into the grid.
Are you familiar with the Same Origin Policy:
http://en.wikipedia.org/wiki/Same-origin_policy
Basically it restricts websites to do AJAX requests to other domains than the html page was loaded from. Common solutions to overcome this are CORS and JSON-P. However, remember that these restrictions are made for security reasons.
I am trying to save a persistent variable for iWidget instances in IBM Connections 4.0
Documentation (link & link) leads me to the following javascript (run with the iWidget in Edit mode):
this.iContext.getiWidgetAttributes().setItemValue("instance","helloWorld");
this.iContext.getiWidgetAttributes().save(); //or .commit(); as save is deprecated
I also tried defined the variable in the widget XML definition:
<iw:itemSet id="attributes" private="false" onItemSetChanged="itemSetChanged">
<iw:item id="instance" value="" readOnly="false"/>
</iw:itemSet>
This sets the value correctly in the local instance, I also see a PUT request to the server to save this value. It returns a 404 Response code. The URL is:
/connections/opensocial/common/repos?st=default%3AcQitETUij2Iqg0A_8mB9A35-pRKmnH_dFUgT4rY-hERIC3ZTNW3hp0OeLr_SYZ2mXWW6OjMtcFPijI_YaIaCDZlduzYgn5FkYQUTiqngHgLqsBMG&type=itemSet&pageId=undefined&widgetId=widget_d785df84b58d4d459707a048014567f6_1369275060798&itemSetId=attributes
The value is no longer stored when I reload the page and try to retrieve it again using:
this.iContext.getiWidgetAttributes().getItemValue("instance");
I notice there is a "pageId=undefined" in the URL.
There are no outputs in the SystemOut.log of the Connections servers.
At the moment this is running in the Homepage "My Widgets" page, but will also be run in Communities application later.
Thanks
For anyone else that comes across this problem, here is what I found;
It turns out that saving through the Homepage refused to work, however I did successfully save Instance Data when the widget is loaded through the Communities mechanism;
JavaScript for saving (.save calls a callback function, but not necessary):
if(this.inCommunity)
{
this.iContext.getiWidgetAttributes().setItemValue("instance",contentToSave);
this.iContext.getiWidgetAttributes().save(dojo.hitch(this,this.dashboardSaved));
}
Loading saved data:
this.instanceData = this.iContext.getiWidgetAttributes().getItemValue("instance");
Widget definition (in widgets-config.xml)
<widgetDef defId="Dashboard" description="MyDash" modes="view edit" url="/Dashboard.xml" uniqueInstance="false">
<itemSet>
<item name="instance" value=""/>
</itemSet>
</widgetDef>
Dashboard.xml
<iw:iwidget xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget" iScope="Dashboard" supportedModes="view edit" mode="view" allowInstanceContent="true">
<iw:resource uri="./dashboard.js"/>
<iw:event id="view" handled="false" onEvent="onView"/>
<iw:event id="edit" handled="false" onEvent="onEdit"/>
<iw:event id="onRefreshNeeded" handled="true" onEvent="onRefresh"/>
<iw:itemSet id="attributes" private="true" onItemSetChanged="itemSetChanged">
<iw:item id="instance" readOnly="false"/>
</iw:itemSet>
<iw:content mode="view">
<![CDATA[<div id="RootWidget"></div>]]>
</iw:content>
<iw:content mode="edit">
<![CDATA[<div id="RootWidget"></div>]]>
</iw:content>
</iw:iwidget>
I finished Ryan Bates #348 video for creating a JSON API using the rails-api gem. I have it working as in the example. However, in his example he has the page that calls the API in the same project. My goal is to separate out the client app from the API app.
I created a second rails app that simply includes the page that does a JSON request for the API data and a post when submitting the form. I have the client app running on localhost:3000 and the API running on localhost:4000.
Below is the client side code. It successfully submits a new deal record, but the
GET doesnt load the list of deals. When looking in the logs it appears it is requesting it as HTML. When the page was apart of the same API project, the same code was making the call as JSON in the logs.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
$(function() {
function addDeal(deal) {
$('#deals').append('<li>' + deal.name + '</ul>');
}
$('#new_deal').submit(function(e) {
$.post('http://localhost:4000/deals', $(this).serialize(), addDeal);
this.reset();
e.preventDefault();
});
$.getJSON('http://localhost:4000/deals', function(deals) {
$.each(deals, function() { addDeal(this); });
});
});
</script>
<div id="container">
<h1>My Deals</h1>
<form id="new_deal">
<input type="text" name="deal[name]" id="deal_name">
<input type="submit" value="Add">
</form>
<ul id="deals"></ul>
</div>
Because of Cross Origin Policy you have following options:
Use jsonp (don't do this since you have your server :) check below )
Manage Cross Origin Resource Sharing on server, recently I wrote answer here how to achieve this
You could use rails ActiveResource::Base to conect to your api, but it may be slow, and you would repeating yourself unless there is some presentation logic you need on backend. BTW, check Nibbler gem it may be somewhat better... it really depends what you need to do in backend.
Anyhow. I would avoid approach 1, its kinda overhead especially if you want to POST, PUT or DELETE, and you can allows use option 2 if you have pure javascript app running as UI. But even if you are building JS agnostic app you always need a bit of backend processing so option 3 is probably something you'd prefer.
Is there a Chrome extension post install hook/API function that will let me perform an action after the plugin is installed or updated?
I would like to perform an action after my extension is installed, and only right after it is installed. This action should only be performed once (post-install or post-update) of the extension.
Update
Some people have proposed setting the version of the extension in localStorage, the problem that I have is that the content script that has access to localStorage is not loaded into the page when the plugin is first installed.
AFAIK after a plugin is installed, and it makes use of a content script injected into the tab/page, the page has to be reloaded.
I don't know how to access localStorage from the background page; localStorage can only be accessed from a content script.
To get the version number from the background page to the content script requires the use of chrome API function to execute scripts:
chrome.tabs.executeScript(null, {code:function_to_execute}, function() { // callback });
However, when you install a plugin, and the page that this plugin needs to inject a content script into is already loaded, it does not inject the content script, you have to reload the page.
update 2
Looking at some of the tips provided in more detail, for the purpose of saving the version number, it is possible to access the localStorage of the background page. However, for what I need to do, which is reload a specific tab at a specific URL (in order to make sure the content script is the newest version) after installing or updating a plugin, it ended up being unnecessary to bother with localStorage.
For the sake of staying on topic, the advice given about writing the version number to localStorage (in the background page) and then checking against the version number in the manifest file is good enough to allow someone to run a script the first time it is installed/or updated.
HowTo
Make manifest file available to the background page (note: this is taken from somewhere else, I don't take credit for it, but I can't remember the source, if you know, let me know and I will add it).
// MAKE MANIFEST FILE AVAILABLE
chrome.manifest = (function() {
var manifestObject = false;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
manifestObject = JSON.parse(xhr.responseText);
}
};
xhr.open("GET", chrome.extension.getURL('/manifest.json'), false);
try {
xhr.send();
} catch(e) {
console.log('Couldn\'t load manifest.json');
}
return manifestObject;
})();
Now you can access your version number like this: chrome.manifest.version
To write to localStorage just pass it in like so: localStorage['my_plugin_version'] = chrome.manifest.version
You can do this using a background page. When the extension is installed, the background page is opened in the background, and thus executed. To make sure it's not executed every time, simply store a value in localStorage.