Building a remote jQuery UI widget with requirejs dependencies - javascript

I've created a jQuery UI widget which is dependent on some other custom JavaScript modules. I fetch these modules using requirejs during the "_create" method of the widget. This actually works great if, I have my end consumers define my "data-main" property. However, in the situation where my consumers are using requirejs on their own, and defining their own "data-main" property, this doesn't work.
Since I'm using requirejs to inject scripts via my widget from a totally different server, I run into a few problems with requirejs's normal way of dealing with this.
First, I can't use a package.json file unless I assume that all of my consumers have a package.json which contains the exact same resources as I have. On top of that, I've got DEV, TEST and PROD server URLs to deal with.
Second I can't use require.config to set my baseUrl during a load on their server, cause it may break everything that they are using require for.
The current implementation I have working requires the consumer to add a script reference to require with my data-main location (external server). Then add a script ref to my widget (external server). This works because nobody else at my company has ever even heard of requirejs :). The second I start showing them how to bundle all of their code into reusable JavaScript modules my solution is broken.
I want to come up with a solution whereas the end consumer can simply reference my single JavaScript widget, which in turn loads everything it needs to function.
Any suggestions on how to do this? I've thought about hacking my own version of require with a static data-main, then just assume they can have multiple requirejs libs. I WOULD HATE TO DO THAT...but I can't really think of a better way.

Here is what I am going to do...
Couple of notes:
I'm using the jQuery UI widget factory pattern (but this isn't exactly a widget)
The widget code lives on a remote server and consumers only reference it, don't download it
I'm using requirejs to load widget dependencies
I want the greatest ease-of-use for the consuming developer
Since it's required that my jQuery UI widget be loaded ASAP so that the consumer has the context of the widget right away ( $(selector).mywidget ) I've decided to tackle my problem inside of the _create method.
This code basically installs requirejs if it doesn't exist, then uses it to install an array of requirements which the aforementioned widget needs to consume. This allows me to assume that the end user can reference my "widget" script by URL, tack on a "data-requiremodule" attribute of the same name, and get a complete list of remote dependencies.
_create: function () {
var widget = this;
widget._establish(widget, function () {
widget._install(widget);
});
},
_getBaseURL: function (scriptId, callback) {
var str = $('script[data-requiremodule="' + scriptId + '"]').attr('src');
if (callback) callback(str.substring(str.search(/scripts/i), 0));
},
_require: function (requirementAry, baseUrl, callback) {
require.config({ baseUrl: baseUrl });
require(requirementAry, function () {
if (callback) callback();
});
},
_establish: function (widget, callback) {
if (typeof require === 'undefined') {
widget._getBaseURL(widget._configurations.widgetName, function (baseUrl) {
var requireUrl = baseUrl + 'scripts/require.min.js';
baseUrl = baseUrl + 'scripts/';
$.getScript(requireUrl, function (data, textStatus) {
widget._require(widget._configurations.requiredLibs, baseUrl, function () {
callback(textStatus);
});
});
});
}
},
I'm not showing my "_configurations" object here...but you get the idea. I hope this helps someone else besides me :).

Related

How do you use an npm package with require & module export as a plain JS library

I'm not sure I'm even asking the right question here, sorry, but I think the two general ones are:
In what way do you need to modify a node.js package using require etc to be used as a plain embedded script/library in HTML?
How do you call a class constructor (?) in JS as a function to validate a form field?
I'm trying to use this small JS library NoSwearingPlease (which is an npm package) in an environment with no node or build system – so I'm just trying to call it like you would jQuery or something with a script & src in the HTML, and then utilise it with a small inline script.
I can see a couple of things are required to get this working:
the JSON file needs to be called in a different way (not using require etc)
the checker variable needs to be rewritten, again without require
I attempted using jQuery getJSON but I just don't understand the class & scope bits of the library enough to use it I think:
var noswearlist = $.getJSON( "./noswearing-swears.json" );
function() {
console.log( "got swear list from inline script" );
})
.fail(function() {
console.log( "failed to get swear list" );
})
noswearlist.done(function() {
console.log( "done callback as child of noswearlist variable" );
var checker = new NoSwearing(noswearlist);
console.log(checker);
});
Please halp. Thanks!
No need to modify, when outside of node the class is just appended to window (global):
fetch("https://cdn.jsdelivr.net/gh/ThreeLetters/NoSwearingPlease#master/swears.json").then(response => {
return response.json();
}).then(data => {
var noSwearing = new NoSwearing(data);
console.log(noSwearing.check("squarehead"));
});
<script src="https://cdn.jsdelivr.net/gh/ThreeLetters/NoSwearingPlease#master/index.js"></script>
In the future, you can answer this type of question on your own by looking through the source code and looking up things you don't understand. That being said, here's what I was able to gather doing that myself.
For your first question, if you have no build tools you can't use require, you have to hope your NPM package supports adding the class to the window or has a UMD export (which in this case, it does). If so, you can download the source code or use a CDN like JSDelivr and add a <script> tag to link it.
<script src="https://cdn.jsdelivr.net/gh/ThreeLetters/NoSwearingPlease#master/index.js"></script>
I'm having a hard time deciphering your script (it has a few syntax errors as far as I can tell), so here's what you do if you have a variable ns containing the JSON and the string str that you need to check:
var checker = new NoSwearing(ns);
checker.check(str);
As an aside, you should really use build tools to optimize your bundle size and make using packages a lot easier. And consider dropping jQuery for document.querySelector, fetch/XMLHttpRequest, and other modern JavaScript APIs.

JavaScript intercept module import

I have a SPA (in Aurelia / TypeScript but that should not matter) which uses SystemJS. Let's say it runs at http://spa:5000/app.
It sometimes loads JavaScript modules like waterservice/external.js on demand from an external URL like http://otherhost:5002/fetchmodule?moduleId=waterservice.external.js. I use SystemJS.import(url) to do this and it works fine.
But when this external module wants to import another module with a simple import { OtherClass } from './other-class'; this (comprehensiblely) does not work. When loaded by the SPA it looks at http://spa:5000/app/other-class.js. In this case I have to intercept the path/location to redirect it to http://otherhost:5002/fetchmodule?moduleId=other-class.js.
Note: The Typescript compilation for waterservice/external.ts works find because the typescript compiler can find ./other-class.ts easily. Obviously I cannot use an absolute URL for the import.
How can I intercept the module loading inside a module I am importing with SystemJS?
One approach I already tested is to add a mapping in the SystemJS configuration. If I import it like import { OtherClass } from 'other-class'; and add a mapping like "other-class": "http://otherhost:5002/fetchmodule?moduleId=other-class" it works. But if this approach is good, how can I add mapping dynamically at runtime?
Other approaches like a generic load url interception are welcome too.
Update
I tried to intercept SystemJS as suggest by artem like this
var systemLoader = SystemJS;
var defaultNormalize = systemLoader.normalize;
systemLoader.normalize = function(name, parentName) {
console.error("Intercepting", name, parentName);
return defaultNormalize(name, parentName);
}
This would normally not change anything but produce some console output to see what is going on. Unfortunately this seems to do change something as I get an error Uncaught (in promise) TypeError: this.has is not a function inside system.js.
Then I tried to add mappings with SystemJS.config({map: ...});. Surprisingly this function works incremental, so when I call it, it does not loose the already provided mappings. So I can do:
System.config({map: {
"other-class": `http://otherhost:5002/fetchModule?moduleId=other-class.js`
}});
This does not work with relative paths (those which start with . or ..) but if I put the shared ones in the root this works out.
I would still prefer to intercept the loading to be able to handle more scenarios but at the moment I have no idea which has function is missing in the above approach.
how can I add mapping dynamically at runtime?
AFAIK SystemJS can be configured at any time just by calling
SystemJS.config({ map: { additional-mappings-here ... }});
If it does not work for you, you can override loader.normalize and add your own mapping from module ids to URLs there. Something along these lines:
// assuming you have one global SystemJS instance
var loader = SystemJS;
var defaultNormalize = loader.normalize;
loader.normalize = function(name, parentName) {
if (parentName == 'your-external-module' && name == 'your-external-submodule') {
return Promise.resolve('your-submodule-url');
} else {
return defaultNormalize.call(loader, name, parentName);
}
}
I have no idea if this will work with typescript or not. Also, you will have to figure out what names exactly are passed to loader.normalize in your case.
Also, if you use systemjs builder to bundle your code, you will need to add that override to the loader used by builder (and that's whole another story).

Lazily Load CSS and JS

I have written this piece of JS and CSS loading code and I would like some advice on it. Anything some of the Javascript Gurus could possibly point out would be much appreciated. The code works, but I have not done extensive testing, because I am concerned about replacing functions in this manner.
A single javascript file containing JQuery as well as the below code will be included on all the pages. We write all the components in house and keep them very modular separated into their own folder with the corresponding JS and CSS. You can imagine starting to use for instance a dropown, dialog and a datepicker on one page would require us to add 6 includes and this quite frankly is annoying, because I want the dependencies to resolve automatically and using JSP includes could possibly make multiple calls to the same resources.
Below is the src to load a single datepicker lazily
;(function($){
//All Lazily loaded components go here
$.fn.datepicker = function(settings){
console.log("This should only be displayed once");
loadCSS("/res/component/datepicker/datepicker.css");
var elem = this;
return loadJS("/res/component/datepicker/datepicker.js",
function(){return elem.datepicker(settings)});//After Load Completion the $.fn.datepicker is replaced
//by the proper working implementation, execute it and return it so we maintain the chain
};
}(jQuery));
function loadCSS(absoluteUrl){
if(loadCSS[absoluteUrl])
return;//Css already loaded
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', absoluteUrl);//Appending entire element doesn't load in IE, but setting the href in this manner does
loadCSS[absoluteUrl] = true;//Memoize
}
function loadJS(absoluteUrl, onComplete){
if(loadJS[absoluteUrl])
return;//Script already loaded
loadJS[absoluteUrl] = true;//Memoize
var result;
jQuery.ajax({
async : false,//Synchronized because we need to maintain the JQuery chain
type :'GET',
url : absoluteUrl,
dataType :'script',
success : function(){
result = onComplete();
}
});
return result;
}
Have you looked in to Require JS, it will send async requests for only the modules you need for a given module.
In addition, because dependencies are scoped to the callback function, namespaces clashing is less of an issue
Typically you would have:
require(["jquery", "foo", "bar"], function($, foo, bar){...});
which allows your code to remain modularized both server side, and client side, in separate locations.
Of course, you need to set up require on your server with a config (described in the webpage), and wrap your resources in define blocks:
define("foo", ["jquery"], function($){...});
The downside is performance on pages that require many modules. In this situation you benefit more from having all resources in combined files, but note that query strings will cause the browser not to cache files in any case.. which is also another performance consideration.
Hope that helps
ps. In terms of CSS lazy loading, you could always use javascript to inject link tags into the head adhoc, and provide some javascript interface functions that your other code can call in order to request a CSS dependency dynamically.

How can I make requirejs polymorphically load a module?

I am currently developing a single page application using Cordova 3.4.0, Requirejs and Backbone. While porting my application from iPhone to iPad, I need to change some functions in some views and keep other parts intact.
To keep the change minimal, my solution is to create new object for each view I need to change, inherit all properties from original view and override only necessary functions.
To do so, I need to configure Requirejs so that in iPad, if I require, for instance, 'user/view/edit-profile.js', it will check whether there was a 'user/ipad/view/edit-profile.js' file, if there is one, requires it, otherwise require 'user/view/edit-profile.js'.
I have tried i18n, but it is not right for this situation. I am coming up with an idea of creating a new plugin for requirejs to do the task.
Does anyone have any suggestion for my problem?
Btw, Since the required file changes dynamically according to the platform. I call it polymorphism.
You could use path fallbacks:
paths: {
"user/view/edit-profile": ["user/ipad/view/edit-profile", "user/view/edit-profile"]
}
The above will make RequireJS try to load the ipad variant first. If as you develop your application you end up with logic to complex for fallbacks, you can use errbacks:
function onload(module) {
// Whatever you want to do...
};
require([module_a], onload, function (err) {
require([module_b], onload);
});
The code above will try to load a module from module_a and then from module_b. I use this kind of code to load modules with names that are computed at run time.
Since every module is a Backbone View, I come up with a solution, which will override extend function of Backbone View to return modified object depending on the existence of ipad, iphone or android dependencies.
The solution will require that if a base view have iPad version, it have to declare the iPad versions at dependencies, the extend function will extend existing view by the iPad view so that the iPad view can inherit all properties of base view and override only necessary functions.
I name the view PolyplatformView. Each view need to declare its ID, for example: user/view/edit-profile
Backbone.PolyplatformView = Backbone.View.extend({
});
Backbone.PolyplatformView.extend = function() {
if(arguments[0].moduleId) {
var extendPropsPath = arguments[0].moduleId.replace('/', '/' + constants.platform_name + '/');
// turn user/view/edit-profile -> user/ipad/view/edit-profile
if(require.defined(extendPropsPath)) {
_.extend(arguments[0], require(extendPropsPath));
}
} else {
console.warn('No module Id in polyplatform view -> cannot extend its parent');
}
var extended = Backbone.View.extend.apply(this, arguments);
return extended;
}

Autoloading Javascript

I have a interesting concept I was working on and looking over, through various stack questions on auto loading JavaScript. I dint want to use a third party tool, aside form jquery, so I thought I would role my own. The concept I have is:
var scripts = {
'name' : 'path/to/script_dir/' // Load all scripts in this file.
}
requireScripts(scripts); // Requires all scripts
// Call your classes, methods, objects and so on ....
The requireScript() function would work something like:
function requireScript(hash){
$.each(hash, function(key, value)){
$.ajax({
url: value,
dataType: "script",
async: false,
error: function () {
throw new Error("Could not load script " + script);
}
});
});
}
Note: The above is just a concept, I don't think it will work.
The above would let you load SPECIFIC scripts. so in essence your hash key value would be 'name' : 'path/to/specific/script'. The issue this posses is that your hash would get rather large ....
The other issue I ran into is what if I simplified this to "php pear naming standard" so, as the trend seems to be - we would create a class, and it would be named after its location:
var some_folder_name_class = function(){}
Would be translated by the autoloader as: some/folder/name/class.js and then loaded that way.
To wrap up and get to my point there are two ways of loading javascript file I am looking at, via rolling my own "require" method. One is loading a directory of javascript files via the hash idea implemented above. (the provided code sample of how this hash would be walked through would have to be changed and fixed....I dont think it works to even load a single file)
OR
to have you just do:
new some_class_name() and have a global function listen for the new word, go find the file your trying to call based on the name of the class and load it, this you never have to worry - as long as you follow "pear naming standards" in both class and folder structure your js file will be loaded.
Can either approach be done? or am I dreaming to big?
I see a lot of frameworks do a bunch of require('/path/to/script') and if I could role my own autoloader to just allow me to either load a directory of js files or even have it where it listens for new before a class instantiation then I could make my life SO MUCH easier.
Have you consider using requirejs and probably Lazy loading.
http://www.joezimjs.com/javascript/lazy-loading-javascript-with-requirejs/
Here is sample version:
You can download here.
The sample is based on this folder structure :
public
index.html
scripts
app.js
lib
** jquery-1.10.2.js
** require.js
3 . From Code:
html
`<!DOCTYPE html><html>
<head><title>Sample Test</title>`
<script src="scripts/lib/require.js"></script> <!-- downloaded from link provide above-->
<script src="scripts/app.js"></script></head>
`<body><h1>My Sample Project</h1><div id="someDiv"></div></body></html>`
application configuration app.js
requirejs.config({
baseUrl: 'scripts',
paths: {
app: 'app',
jquery: 'lib/jquery-1.10.2' //your libraries/modules definitions
}
});
// Start the main app logic. loading jquery module
require(['jquery'], function ($) {
$(document).on('ready',function(){
$('#someDiv').html('Hello World');
});
});
jQuery-only option
If you are looking for a jQuery-only solution, have a look at jQuery.getScript(). It would be a great candidate for handling the script loading portion of your problem. You could then write a very small wrapper around it to load all the scripts—something like you wrote above:
var loadScripts = function(scripts) {
$.each(scripts, function(name, path) {
jQuery.getScript("/root/path/" + path + ".js");
})
}
If you are interested in more information on this approach, read this article by David Walsh.
Other great libraries
I strongly recommend taking a look at the current batch of script-loading libraries. I think that you will pleasantly surprised by what is out there. Plus, they come with the benefit of great community support and documentation. RequireJS seems to be the front runner but David Walsh has great articles on curl.js and LABjs.

Categories