Declaring class differently for different Dojo versions without duplicating code? - javascript

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.

Related

Why does my feature support test not run before syntax error occurs?

I'm doing Javascript feature support testing on my site, but I'm running into an issue while testing on IE11 that is causing a syntax error (because of a default parameter) prior to executing my feature test.
My application builds a script tag to inject into the layout view. That script tag is built using a configuration that defines all of the JS dependencies. I've ensured that the resulting JS file that's delivered to the browser has my feature detection at the top of the combined script (right after jQuery). But what seems to be happening is that some function that's defined later on in the script is running (or parsed?) prior to running my feature detection expression.
For more clarity, this is an example of what my script tag looks like:
<script type="text/javascript" src="/asset/jscript?load=feature-detection,global,login&version=1820523616"></script>
Which results in a script file that first contains jQuery, then my feature-detection.js, then everything else. This is the line in one of the JS files after feature detection that causes the syntax error:
processMessages: function(problem, container, useMessage = true) {...}
EDIT:
To be clear, I'm unsure why the syntax error would occur before the feature detection logic, even though my feature detection occurs far earlier in the code. This is what my combined script looks like:
/* jQuery v3.2.0 ... */
// This is a placeholder for jQuery code
// Test browser support for 'for...of' statement.
// If this feature is lacking, ES6 support is lacking from the browser
try {
eval("for (var i of []);");
} catch(ex) {
// Show 'Unsupported Browser' banner
alert('GET BETTER BROWSER');
}
// The rest of my JS files, which would contain several non-supported features
processMessages: function(problem, container, useMessage = true) {...}
The "alert" is never triggered in IE11, instead I get "Expected ')'" pointing to the default parameter function
I'm pretty sure you can't use default parameter values in JS like that, that's probably what's breaking in IE.
You'll have to default it like this instead:
processMessages: function(problem, container, useMessage) {
if (typeof useMessage === 'undefined') useMessage = true;
}
Javascript is first compiled then executed, it's a multistage process.
Your syntax error will occur at compilation cycle(or interpret time) which is why you are seeing it before your feature detection executes.
I am not a deep js expert so my terminology might be wrong but that's the idea behind your issue

Angular Universal and browser feature checks

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.

Firefox bootstrapped extension, race conditions when importing in top-level?

I'm trying to get myself familiar with Firefox bootstrapped add-ons. Consider following example:
// bootstrap.js
'use strict'
function alert(message) {
var prompts = Components.classes["#mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
prompts.alert(null, "from my extension", message);
}
try {
Components.utils.import('chrome://my-ext/content/foo.jsm');
alert('ok');
} catch(e) {
alert(e);
}
And chrome://my-ext/content/foo.jsm being just this.EXPORTED_SYMBOLS = [];.
The issue with an above code sample is that it does not work each time. It may fail with NS_ERROR_FILE_NOT_FOUND instead of importing, or may say OK — despite the fact later, when browser starts, I can access foo.jsm through location bar.
Does this mean I should not import anything in the top level, because chrome registration might not be done yet, or problem is in something else?
N.B. The following is my experience and may not be definite
bootstrap.js of bootstrapped addon run when browser starts and BEFORE any WINDOW or DOM is created.
bootstrap.js also has a order and specific format for execution.
Is above example the way your bootstrap.js is?
You can import Firefox modules anywhere (although I would always do so on top).
It is best to execute your Components.utils.import('chrome://my-ext/content/foo.jsm'); within function startup(data, reason) { ... }
Additional Notes/Suggestions:
Generally, I would assign var prompts outside a function so that it is not reassigned every time a function is run.
I would also use Services.jsm for ease of use (but it make no difference at all) eg:
Components.utils.import('resource://gre/modules/Services.jsm');
// then anywhere in the code
Services.prompt.alert(null, title, text);
Habitually, I do all Firefox built-in modules imports Components.utils.import() on top of the JS/JSM
Firefox built-in modules (not addon's modules) are cached by Firefox and there is no need to Components.utils.unload() them.

Easy optional dependency on jQuery for RequireJS between Node.JS and browser

I'm using RequireJS, and trying to pack up a jQuery widget for easy usage into one file. Inside the widget's JavaScript code are a certain number of non-UI functions that don't call $-anything, that I'd like to export and be able to use on the server side.
(The shared routines that don't depend on jQuery used to be in a separate module called client-server-common.js. But like I said, I'm looking to reduce the number of files...and there's no real reason to be hung up on including the dead code for the widget on the server. So the widget can just subsume the common code.)
I'd like my only dependencies for the widget to be jQuery and underscore, with jQuery optional (and it degrades into what was client-server-common.js in that case). So the interface I'm looking for would be like:
require(['jquery', 'underscore'], function ($, _) {
var exports = {...};
if ($) {
// Do the stuff that only makes sense in the client
// Totally fine if there is no jquery; in that case
// all we probably care about are the exports
...
}
// Do the stuff for both client and server
...
// Return the exported functions
return exports;
}
Reading up on what others have asked, I notice this answer to this question from January says "You cannot really set it optional":
requireJS optional dependency
That's not what I want. Is that the last word? :-/
It seems that I can get around this by installing the jquery NPM package, which apparently does stuff with the DOM somehow:
Can I use jQuery with Node.js?
My understanding would be that if I added this dependency and just ignored it, I'd be okay. I might even find that there was some good reason to do DOM manipulation on the server (I haven't thought enough to figure out why or how that would be useful, is it?)
So should I add the dependency for simplicity's sake, and just not use it? Or can I rig it up so that I just give a jQuery of null on the server configuration, through some magic that doesn't involve waiting for timeouts and errors and hacks? Could someone make a "jquery-null" package that somehow just came back and gave you a {} or null for jQuery to smooth over situations like this?
Advice appreciated! :-/
The answer you mention says "You cannot really set it optional" and then gives a solution to operate even in the absence of modules. You can use an errback that will do nothing if running server-side.
The following code assumes that RequireJS' require call is available as requirejs. When loading RequireJS in a browser, this is the default (that is, after loading RequireJS requirejs === require is true.) Server-side, you would have to make it available with something like:
if (typeof window === "undefined")
var requirejs = require('requirejs');
(The above code would obviously fail if there is something in Node that sets window globally. I've never run into this problem.)
Once the above is taken care of we can do:
requirejs(['underscore'], function (_) {
var exports = {...};
requirejs(['jquery'], function ($) {
// This will execute only if jquery is present.
// Do the stuff that only makes sense in the client
// Totally fine if there is no jquery; in that case
// all we probably care about are the exports
...
}, function (err) {
// This will execute if there is an error.
// If server-side, do nothing. If client-side, scream!
});
// Do the stuff for both client and server
...
// Return the exported functions
return exports;
});
The answer you mentioned and RequireJS' documentation (which I linked above) mention checking the id of the failed module and undefining it. I'm quite certain that you would not need to undefine it since you won't try to load it again from a different place. Checking the module id would be a way to future-proof your code if someday jQuery depends on some other thing. Right now, loading jQuery just loads jQuery so if there is a failure it cannot be any other module than jQuery.
I would not include the actual code of jQuery server-side unless I'd have an actual substantial reason for it. What I would do if I wanted to get rid of the errback, for whatever reason, would be to have a build of my code for server-side use that includes a fake jQuery module. Something like:
define(function() {
return "I'm totally fake";
});
And then test it:
requirejs(['jquery'], function ($) {
if ($ !== "I'm totally fake") {
// Do the real deal.
}
});
If you do eventually need jQuery server-side, you'll have to also install something like jsdom. It used to be that installing jQuery with npm install jquery would include jsdom in the installation but I think this has changed recently.
I'm using RequireJS, and trying to pack up a jQuery widget for easy usage into one file. [..] I'd like my only dependencies for the widget to be jQuery and underscore, with jQuery optional [..] Reading up on what others have asked, I notice this answer to this question from January says "You cannot really set it optional" [..] That's not what I want. Is that the last word? :-/
No it's not the last word
This can be done elegantly and simple actually. I've described how to use an AMD module only when it's loaded in my answer to that question, but I'll repeat the gist of it here:
define(['require'], function(require){
if (require.defined('jquery') {
var $ = require('jquery');
$.fn.something = function(){};
}
});
We don't add the dependency directly, but instead manually require it only when it's already defined so we don't trigger any requests.
This form in which we declare a dependency on require, then use that inside our module is actually recommended by the RequireJS author, but my experimentation indicates that this actually also works:
define(require.defined('jquery') ? ['jquery'] : [], function($){
if ($) {
// Yippee, all jQuery awesomeness is available
}
});

Error loading KnockoutJS as part of a third party package when host site uses RequireJS

I've developed a JavaScript plugin to be included on our customers' websites. The plugin I've created depends on some external libraries, which are bundled and delivered to the client as one big package: jQuery 1.8.2 and KnockoutJS v3.0.0.
The plugin plays fine on most sites, but if the host site uses RequireJS, my package fails to load because KnockoutJS automatically detects that RequireJS exists and attempts to use it. Here is the error that gets thrown:
Mismatched anonymous define() module
Obviously, I've found an "explanation" of the error message on the RequireJS site. Unfortunately, I don't understand how to avoid it. In my local copy of the KnockoutJS library, I've found the offending line:
(function(factory) {
// Support three module loading scenarios
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
// [1] CommonJS/Node.js
var target = module['exports'] || exports; // module.exports is for Node.js
factory(target);
} else if (typeof define === 'function' && define['amd']) {
// [2] AMD anonymous module
define(['exports'], factory);
} else {
// [3] No module loader (plain <script> tag) - put directly in global namespace
factory(window['ko'] = {});
}
}
If I manually edit this file so that condition [2] never executes and only condition [3] every executes, then everything works fine. Of course, I don't want to do this because it requires me editing an external library, which I'd prefer to keep in pristine condition so I can upgrade it later.
I have a feeling there may be a way to make this work, I just don't understand how RequireJS works. Obviously, KnockoutJS is TRYING to play nice with RequireJS, but in my case, it's failing. For me, in this case, even though RequireJS exists, I don't need KnockoutJS to use it.
How can I get these two libraries to work side by side?
EDIT
I have no control over when my library is loaded vs. all other libraries the host site already loads. In fact, most of the time my plugin will be included, it will be by someone with NO web dev experience using a terrible WYSIWYG platform like WordPress, Webs.com or Weebly so sometimes my script tag might make it to the top of the head element, other times it might be included in the body element somewhere.
Also, to be clear, my library does NOT use RequireJS. It just so happens that one of our customers that is trying to use my library DOES use RequireJS and when my library gets included, KnockoutJS (bundled with my library, but NOT already on the host site) throws an exception because it thinks it needs to register itself with RequireJS (or at least that's my speculation as to the exception).
While, in principal, I'm not opposed to loading the libraries my code depends on on demand, the truth is that it will create a slow, poor experience for my users as it will take additional request/response cycles to load them.
Well, the easiest thing to do would probably be to load knockout before requirejs. ko will no longer detect that require is present, and will go with option [3]. If you can't do this, the other option is to add your plugin and ko file in a require hierarchy.
So let's say that you plugin looked like this:
(function(ko){
//stuff
ko.applyBindings({});
})(ko)
You would need to change it to this:
require([
"knockout-3.0.0.js" // this should be the url you use for knockout
], function(ko){
//stuff
ko.applyBindings({});
})
and NOT load the knockout.js file as a separate tag. Require will handle the loading. The server must still be able to deliver the "knockout-3.0.0.js" url of course. This is how require works. It loads whatever urls you pass as elements in the array parameter of require, and passes what they return as parameters to the function.
If you need to minify/bundle both the plugin file and the ko file into a single file, you can use the reuquirejs minifier/optimizer (http://requirejs.org/docs/optimization.html). It will navigate the dependency tree and output only one js file with all modules inside. One quirk here: you need to drop the .js extension for the minifier to work, read more about it the documentation, I just mentioned it to save some headaches.
Also, more documentation on how to use ko with require can be found here: http://knockoutjs.com/documentation/amd-loading.html
EDIT, after op edit:
OK, so in this case you should create a separate scope, in which you can do what you want. You'll need to copy the ko code inside your file, but like this you'll at least get a single file.
So, first create a scope:
(function(){
})()
Then copy ko code inside:
(function(){
//ko code here, should be a single, minified line
})()
Then you need to trick ko into using option 3, so do this:
(function(){
var define = null; //so define will no longer be a function, don't forget the var
var require = null;
//ko code here, should be a single, minified line
})()
Optionally, you might also want to reassign window in the step above, if you don't want ko to be available to the entire page.
And now add your plugin code:
(function(){
var define = null; //so define will no longer be a function, don't forget the var
var require = null;
//ko code here, should be a single, minified line
//plugin code here;
})()

Categories

Resources