Angular Universal and browser feature checks - javascript

When developing a web app with jQuery or normal JavaScript, it is commonplace to check for feature availability first. So for example, if I want to use the document.oncopy event, I should first have something like this to ensure my code doesn't break for lesser browsers:
if ("oncopy" in document) {
// Feature is available
}
I'm a bit puzzled about how this would work in Angular2. I could still use the same if I expect to only run in the browser, but I'm specifically told to leave the DOM alone if I want to use Angular Universal and depend on templates or the DomRenderer instead. This allows the page to be pre-rendered on the server and provides a truly impressive performance gain.
But suppose I want a specific div to be invisible if the document.oncopy is unavailable. My understanding is that this is not recommended:
<div *ngIf="hasFeature()">...</div>
and
hasFeature() {
return 'oncopy' in document;
}
because then I'm still manipulating the DOM. Note that my example is about the document.oncopy but I could choose any feature whatsoever that doesn't have universal support.
I tested this using Chris Nwamba's tutorial on Scotch and added the following to the end of his Home template:
<div *ngIf="hasFeature()">Feature is supported</div>
<div *ngIf="!hasFeature()">Feature is NOT supported</div>
Update: Interestingly, it gave different results on different browsers. On Chrome 55, it executed as it would normally and showed the "Feature is supported" message. On IE11, I received the "not supported" message. In both instances the server log shows a EXCEPTION: document is not defined message, but the page still seems perfectly okay.
So what is the correct way to check for browser features if I want to use Angular Universal?
Update:
I also toyed around with using a field in the template and assigning that field from one of the life cycle hooks. ngAfterContentInit seemed like a fine candidate, but also causes an error on the server. It still runs fine in the browser with no weird effects (that I have noticed so far).

There are two ways to approach this:
Do the check only once the server is done rendering and the client is completely initialised (including the replay of user events done by preboot.js).
Return a reasonable default when the page is running on the server and perform the actual check only in the browser.
I started looking at the first option, but none of the Angular2 life cycle events will help with this. In fact, you can clearly see them all executing on the server and only then on the client.
I then started looking for something usable in preboot.js but quickly realised it was more complex than it needed to be.
So onto option 2 I went. It turns out checking for the browser is as easy as importing and checking isBrowser.
import { isBrowser } from "angular2-universal";
#Component({
// All the usual stuff
})
export class MyComponent {
// ...
hasFeature(): boolean {
return isBrowser && 'oncopy' in document;
}
// ...
}
And then use the template as I showed in the question.
To check if you're running on the server, import and use isNode in exactly the same way. There doesn't seem to be an obvious way to distinguish between Node and ASP.NET Core, but perhaps it's best not to write too much code that specific to platform.

Related

Language switch in SAPUI5

I've got a language problem with my SAPUI5 controls.
If I execute e.g.:
sap.ui.getCore().getConfiguration().setLanguage("de");
My i18n files are loaded correctly and all labels are translated to German. But the controls are still in English.
The only way to get German controls is with the URL parameter:
sap-ui-language=DE
But I can't use a parameter in my case. Any idea?
Please note that sap.ui.getCore().setLanguage() explicitly states
The framework does not guarantee that already created, language dependent objects will be updated by this call. It therefore remains best practice for applications to switch the language early, e.g. before any language dependent objects are created. Applications that need to support more dynamic changes of the language should listen to the localizationChanged event and adapt all language dependent objects that they use (e.g. by rebuilding their UI).
Besides that, I fully support Nabi's answer (but I'm not allowed to vote it up).
I just would like to add that controls (like FilterBar) better should use the hook approach:
FilterBar.prototype.onlocalizationChanged = function(oEvent) {
// .. same bundle update code as in Nabi's proposal
}
Using the hook in controls avoids the need for adding attach + detach calls in init / exit and keeps the event registry small.
I can easily confirm the behavior you described by testing the Explored App Example. There, just open the console and hit sap.ui.getCore().getConfiguration().setLanguage("de");
I also checked the implementation of the FacetFilter and I would call this a bug in the Control implementation. It comes from how the texts are loaded inside the control. Just in case you are interested:
The message bundles all contain the correct translations for FACETFILTER_INFOBAR_NO_FILTERS (for en the translation comes from the "default" bundle):
messagebundle.properties
messagebundle_de.properties
The FacetFilter has a hidden aggregation called SummaryBar. The SummaryBar contains the text you see. Of course, this text comes from a bundle.
However, the bundle is initialized exactly once in init() by calling sap.ui.getCore().getLibraryResourceBundle("sap.m");. Here the API docs say:
If only one argument is given, it is assumed to be the libraryName.
The locale then falls back to the current session locale.
This means the bundle is cached and therefor changes to the localization (e.g. language) do not trigger the bundle to load a new translation file. Thus, we will always see the initial language no matter what we try (even rerendering() does not help).
A solution would be to fix the control by adding the following code right after the the bundle gets loaded inside the init:
sap.ui.getCore().attachLocalizationChanged(function(oEvent){
var oChanges = oEvent.getParameter("changes");
if (oChanges && oChanges.language){
this._bundle = sap.ui.getCore().getLibraryResourceBundle("sap.m", oChanges.language);
this.rerender();
}
}.bind(this));
You can try this out in the explored app linked above, it worked for me just fine...
I just opened an issue on github.

Browser.ExecScript() stopped working after updating windows

I've set up a simple testbed for WatiN (ver 2.1) which reads:
var browser = new IE();
browser.GoTo("http://www.google.co.il"); // webpage doesn't matter really
browser.RunScript("alert(123)");
This works only if KB3025390 is not installed. Installing it breaks the above test with an UnAuthorizedAccessException which has HRESULT set to E_ACCESSDENIED. What gives? Is there any workaround?
Update: Using IWebBrowser2.Navigate2 along with "javascript:console.log(123)" type of scripts works however
it makes me feel uneasy using such a backchannel
the scripts run through this back-channel of .Navigate2() may only have a max length of about 2070 chars (give or take) otherwise they get forcibly truncated to this length leading to javascript errors upon attempting to run them
using .Navigate2(), even with the most trivial script, will clog the ready state of Internet Explorer for good in the sense that it will be set to READYSTATE_LOADING without any hope of getting rid of it. In simple terms this means that once you use this hack, you either have to perform every single subsequent operation in WatiN in a "dont-wait-for-webpage-to-load" fashion (GoToNoWait, ClickNoWait etc) lest your code freezes upon waiting for the browser to turn back to READYSTATE_COMPLETE (which will never come about ofcourse as already mentioned).
there appears to be a much broader issue here in the sense that I can't even access the properties of an IHtmlWindow2 object p.e. window.document throws an unauthorized exception again making it virtually impossible to transfer over to the C# world the return-values of the scripts I'm running (using Expando etc) for documents other than window.top.document (for the window.top.document window there is IWebBrowser2.Document which does the trick)
Update#2: The folks over at the selenium project have also noticed this issue:
https://code.google.com/p/selenium/issues/detail?id=8302
A bug report has been created as well:
https://connect.microsoft.com/IE/feedback/details/1062093/installation-of-kb3025390-breaks-out-of-process-javascript-execution-in-ie11
Update#3: IHTMLWindow2.setInterval and IHTMLWindow2.setTimeout also throw UnauthorizedAccess exceptions. These methods are not marked as deprecated in:
http://msdn.microsoft.com/ko-kr/library/windows/desktop/aa741505%28v=vs.85%29.aspx
yet they have wounded up suffering from the same cutbacks all the same.
Update#4: I gave the approach recommended in this post a shot:
https://stackoverflow.com/a/18546866/863651
In order to dynamically invoke the "eval" method of the IHTMLWindow2 object (or any other method really). Got the same "System.UnauthorizedAccessException" as above. So no joy here either.
Microsoft recommends using "eval" over "execscript" however after the above experiment I suspect that they are refering to accessing "eval" only from within the browser.
As far as I can tell thus far, when it comes to the full-fledged IE11+ using "eval" out-of-process (via COM) appears to have been completely prohibited along with any other function-invocation of the window object, the only exception being the back-channel of the .Navigate2() mentioned above.
It turns out Microsoft eventually backpedaled on its decision to kill off .execScript at COM-level. Just install the latest updates for Windows including kb3025390: One of the updates for IE that came after kb3025390 brings back .execScript functionality at COM-level
Note, however, that .execScript is not accessible through IE's javascript anymore. In that context it's gone for good.
fyi: this one is also not working
ieInstance.Document.Script.<methodNameString>(<commaSeperatedParameterString>)
try this worked for me at some places but not all places
ieObject.Navigate "javascript:<methodNameString>(<commaSeperatedParameterString>)", Null, "_parent"
or
ieObject.Navigate2 "javascript:"<methodNameString>(<commaSeperatedParameterString>)", Null, "_parent"
now trying to find out solution using eval
I have found a way around the problem of an update installing automatically. You can just create a simple batch file with following content.
{code}
#echo off
wusa /uninstall /kb:3025390/quiet /norestart
END
{code}
Then go to task scheduler, create a new task for this batch file to run every one hour or day as per your requirements. Add it as a system task so it runs in the background and does not affect the running automations.

Declaring class differently for different Dojo versions without duplicating code?

I have an iWidget designed for IBM Connections, and my javascript code depends on Dojo (which is included by default in Connections).
It currently works in Connections 4.0 and 4.5, but is broken in Connections 5.0 (released last week), as Dojo has been updated to v1.9 and complains about my use of dojo.require.
These messages appear in the browser console when my widget tries to load on Connections 5.0:
Avoid calling dojo.require() to load classes at runtime, use net.jazz.ajax.xdloader.load_async() instead. Function '(anonymous)' required class 'dojox.atom.io.model'.
Avoid calling dojo.require() to load classes at runtime, use net.jazz.ajax.xdloader.load_async() instead. Function '(anonymous)' required class 'dojox.atom.io.Connection'.
I want to make conditional code that uses different ways of defining my widget class and requiring other Dojo modules depending on the Dojo version.
The widget javascript currently looks like this:
dojo.provide('insightCommunityWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightCommunityWidgetClass',null,{
// Class fields and methods. Currently 680 lines uncompressed.
});
I haven't yet created a version that works with Dojo 1.9 / Connections 5.0, but I think it would look something like this (and I'll have to make my javascript file name match the desired class name):
define(['dojo/_base/declare','dojox.atom.io.model','dojox.atom.io.Connection'], function(declare){
return declare(null, {
// Class fields and methods.
});
});
How can I have both of these in one file and choose between them without duplicating the class body?
Update:
I've attempted some conditional code, checking (define && define.amd) as suggested by Dimitri, tested this on Connections 4.0 and 4.5, and am getting very weird behaviour.
Temporarily ignoring any attempt to not duplicate my class, here's some conditional code which I've used exactly as shown, with a severely reduced widget class:
if (define && define.amd) {
console.log('Declaring insightWidgetClass with AMD (new method).');
define(['dojo/_base/declare','dojox/atom/io/model','dojox/atom/io/Connection'],
function(declare){
return declare(null,{
SVC_INV: 1,
onLoad: function() {
console.log('insightWidgetClass onLoad.');
}
});
}
);
} else {
console.log('Declaring insightWidgetClass with dojo.declare (old method).');
dojo.provide('insightWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightWidgetClass',null,{
SVC_INV: 1,
onLoad: function() {
console.log('insightWidgetClass onLoad.');
}
});
}
This seems not to run at all. None of my console.log messages appear in the browser console.
If I comment out the conditionals and make it so the only active code is the block after else, it runs. I get the "declaring ... (old method)" and the "insightWidgetClass onLoad" console messages.
I thought maybe enclosing the Dojo provide, require and declare calls in any kind of block might cause a problem, so I tested just putting the working code in an if (true) { block, and it still works.
The last things I've tried at this point are adding this one line before everything else, to see what define is:
console.log('dojo define',define);
... which breaks it. No console messages at all from my code.
Then I remove the define argument from that new line, so it's only sending a string to the console, and the code works again.
It seems like any mention of a define identifier silently stops the rest of the code from running.
There are no errors or warnings in the console indicating a problem. All I can say to that is: WTF?!
Now back to checking dojo.version instead.
Normally both should still work, dojo.provide() and dojo.require() are deprecated, but not entirely removed. Just make sure that your loading dojo in synchronous mode.
Besides that, the AMD way of coding is introduced in Dojo 1.7, which means that it should be supported on IBM Connections 4.5 as well (though I don't know about IBM Connections 4).
But if you really want to use both code bases, you can simply refer to the same object in stead of duplicating it, for example:
var myModule = {
// Class fields and methods.
};
if (dojo.version.major == 1 && dojo.version.minor == 9) {
define(['dojo/_base/declare','dojox.atom.io.model','dojox.atom.io.Connection'], function(declare){
return declare(null, myModule);
});
} else {
dojo.provide('insightCommunityWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightCommunityWidgetClass',null, myModule);
}
Or you could use the following check:
if (typeof define === 'function' && define.amd) {
// AMD style code
} else {
// Non-AMD style code
}
This is the approach most cross-loader libraries use. Libraries that work both on AMD loaders (Dojo, Require.js), but also on Node.js or simply by using global namespacing use a similar piece of code to determine how they load their module.
This is not your code, it should work as it is. We recently faced the same problem and identified the cause.
Connections 5 is using an AMD version of the Jazz framework which provides its own dojo loader. This framework is used to aggregate the needed dojo modules into a single JS file, which limits the number of requests to the server. Unfortunately, this loader no longer handles synchronous modules loading. It fails with the warning you reported when dojo.require() requests a module that is not yet loaded by the aggregator. If the module was already loaded, because it was part of the Jazz aggregated file, then it works. It explains why you can dojo.require() some modules, but not all of them.
-> A workaround is to deploy a server side OSGi bundle to get the modules you need part of the aggregated JS file. There is a documented extension point for this. This can unblock you while enhancing the performance of your page.
Now, we opened a PMR to IBM support. The development team is working on a resolution. We hope that they will be able to deliver a fix soon.
We reported the following issues:
dojo.require()
dojo.requireLocalization()
dojo.registerModulePath()/require({paths:})
If you think about something else, please let me know.

Plupload - Show a warning when using Flash Runtime and the Browser has no Flash

I need to show an info message when Plupload decides to use Flash but the Browser doesn't support it.
Currently nothing happens(TM) after plupload.init() and even though the API specifies a "runtime"-property which should name the used runtime I could find no way to find the currently used runtime.
This results in a non-working Upload-Button and the user does not understand whats going on.
What I'm looking for is something like this:
if(plupload.runtime == "flash" && !detect_flash()) {
$("#flash_warning").show();
}
Unfortunately, failed runtime loading attempts do not seem to trigger any event you can subscribe to.
So I don't see any way to subscribe to a failed runtime loading attempt aside from modifying plupload sources.
Open plupload.js or pluploadfull.js, look for the first match of '.init('.
What you want to subscribe to is the 'else' part of the callback function that processes loading attempt (just before calling w() at the end of the following line )
G.init(B,function(H){if(H&&H.success){B.features=F;B.runtime=G.name;B.trigger("Init",{runtime:G.name});B.trigger("PostInit");B.refresh()}else{w()}})
Anyway, best thing to do in my opinion is to follow the examples and have a disclaimer message in the container which is supposed to hold the uploader. This way, if all runtimes fail to load, you will have your disclaimer message.

I would like to interactively detect when an ActiveX component has been installed, and asynchronously refresh a portion of the page

I am working on a website, and I would like to refresh a portion of the page after an ActiveX component has been installed. I have a general idea of how to do this with polling, which I am working on getting going :
function detectComponentThenSleep(){
try{
// Call what I want ActiveX for, if the method is available, or
// ActiveXComponent.object == null --- test for existance
document.getElementById("ActiveXComponent").someMethod();
}
catch{
// Try again, if the method is not available
setTimeout(detectComponentThenSleep, 100);
}
}
However, what I would REALLY like to do is something like this:
ActiveXObject.addListener("onInstall", myfunction);
I don't actually have the source for the ActiveX component, but I have complete control of the page I am hosting it on. I would like to use JavaScript, if possible, to accomplish this.
So, my question is 1.) will this actually work with the polling method? and 2.) Is there an interrupt/listener like way of doing this? I am sure I am missing something with connecting the dots here, I can already detect if the component is present, but I am having trouble doing this asynchronously.
Thank you very much for your time and help,
-Brian J. Stinar-
1.) This didn't work at all with the polling method.
2.) I couldn't find an interrupt / listener way of doing this.
I finally ended up just putting this entire ActiveX compnenent on it's own page. ActiveX does a page refresh on install, so I simply had the default page behavior as the what I wanted to happen when the component wasn't available. This is different than what I was trying to do, but it worked OK for my purposes.
My recommendation to anyone in a similar situation is to just put the ActiveX component on it's own page, and pass data back and forth from this page. Otherwise, you are probably going to have a lot of the problems that I had.
-Brian J. Stinar-

Categories

Resources