Using functions from external JavaScript libraries in Moodle plugins - javascript

I am making a Moodle plugin and wanted to use bowser to detect the user's web browser. I referenced the file by putting
$PAGE->requires->js( new moodle_url($CFG->wwwroot.MOODLE_TINYMCE_RECORDRTC_ROOT.'tinymce/js/bowser.js') );
in the plugin's plugintype_pluginname.php file (placeholders of course), and I call the bowser function from the plugin's module.js file.
When I load the plugin (it appears as a button in TinyMCE), the console throws ReferenceError: bowser not defined, so I'm assuming this means Moodle doesn't make the functions in Bowser globally available.
I read many in many places that I need to wrap my code in an AMD, or something to that effect, but after lots of reading it still goes over my head. Is there any way to make bowser's functions available to the main plugin module?

Note: This works for me in Moodle 3.3.2, ymmv.
Put bowser.js into my_plugin_folder/amd/src/.
When using the original bowser.js I got Uncaught TypeError: bowser._detect is not a function. I don't exactly understand why I get this error but here's one way to fix it: Replace the top code block in bowser.js with this one from umdjs/umd.
Your file should now look like this:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
// module definition here
return bowser
}));
Moodle bundles all JavaScript modules together so that clients don't need to perform a separate HTTP request to get each one. This bundle is called first.js. It contains all modules that aren't lazy-loaded. If you load a Moodle page now it should contain the contents of bowser.js with some values replaced by Moodle.
If you don't want bowser to be loaded on every page, you can just rename it to bowser-lazy.js. Then it should only be loaded when you use it.
You can test if it worked by calling:
require(['plugintype_pluginname/bowser'], function(bowser) {
var ua = bowser._detect(navigator.userAgent);
console.log(ua);
});
Seems like you need to change the require call to use bowser-lazy instead of bowser when you want to use lazy-loading.

Related

This requireJS thing has me confused

Hi i am trying to understand this steganography tool https://github.com/petereigenschink/steganography.js.
I am using this in my react project. The bit about RequireJS has me already confused. Can i turn this to a normal, not so scary javascript?
;(function (name, context, factory) {
// Supports UMD. AMD, CommonJS/Node.js and browser context
if (typeof module !== "undefined" && module.exports) {
module.exports = factory();
} else if (typeof define === "function" && define.amd) {
define(factory);
} else {
context[name] = factory();
}
})("steg", this, function () {}
I need to be able to use this function or whatever it is in my App.js file. Any pointers please?
This is the error that react dev server throws:
Failed to compile.
./src/steganography.js
Line 12: 'define' is not defined no-undef
Line 13: 'define' is not defined no-undef
Since javascript didn't originally include any way of doing modules (ie, to link together code from multiple files), several different approaches have been invented for ways to do so. That block of code is just trying to conform to all of them, so that the codebase will work regardless of which module approach the consumer is using.
The outer part is an Immediately Invoked Function Expression. This is an anonymous function which gets created and then immediately called. In this code, its main purpose is to isolate any variables inside the expression, and thus make it impossible for them to accidentally become global variables.
Inside the IIFE, it has some checks to figure out what kind of module system is being used, so that it can produce the correct output to be imported.

How to circumvent RequireJS to load module with global?

I'm trying to load a JS file from a bookmarklet. The JS file has this JS that wraps the module:
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory);
} else {
// Browser globals
root.moduleGlobal = factory();
}
}(this, function factory() {
// module script is in here
return moduleGlobal;
}));
Because of this, if the webpage uses RequireJS, the script will not export a global when it loads. To get around this I temporarily set define to null, load the script, then reset define to its original value:
function loadScript(url, cb) {
var s = document.createElement('script');
s.src = url;
s.defer = true;
var avoidRequireJS = typeof define === 'function' && define.amd;
if (avoidRequireJS) {
var defineTmp = define;
define = null;
}
s.onload = function() {
if (avoidRequireJS) define = defineTmp;
cb();
};
document.body.appendChild(s);
}
This works, but it seems to me like it could be problematic to change a global variable when other parts of the application could depend on it. Is there a better way to go about this?
You may fetch the script using XMLHttpRequest, jQuery.ajax or the new Fetch API.
This will allow you to manipulate the script and reassign define before executing it. Two options:
Have the module export a global by wrapping the script with:
(function(define){ ... })(null);
Handle the module exports yourself by wrapping the script with:
(function(define, module){ ... })((function() {
function define(factory) {
var exports = factory();
}
define.amd = true;
return define;
})());
You can then load it using a new <script> element or eval 😲.
Note that when using XHR, you may have to address CORS issues.
If you can use the AJAX method above, that will be best. But as stated, you will need to deal with CORS issues, which is not always trivial - even impossible if you do not control the origin server.
Here is a technique which uses an iframe to load the script in an isolated context, allowing the script to export its global object. We then grab the global object and copy it to the parent. This technique does not suffer from CORS restrictions.
(fiddle: https://jsfiddle.net/qu0pxesd/)
function loadScript (url, exportName) {
var iframe = document.createElement('iframe');
Object.assign(iframe.style, {
position: 'fixed',
top: '-9999em',
width: '0px'
});
var script = document.createElement('script');
script.onload = function () {
window[exportName] = iframe.contentWindow[exportName];
document.body.removeChild(iframe);
}
script.src = url;
document.body.appendChild(iframe);
iframe.contentWindow.document.open();
iframe.contentWindow.document.appendChild(script);
iframe.contentWindow.document.close();
}
loadScript('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', 'jQuery');
I ran a quick test to see if a memory leak would happen from deleting the iframe, and it appears to be memory safe. Here's the snapshot of loading a script 100 times, resulting in 100 different iframes and 100 different instances of jQuery loading.
The parent window's jQuery variable is continuously overwritten, meaning only the last one prevails and all previous references are cleaned up. This is not entirely scientific and you will need to do your own testing, but this should be safe enough to get you started.
Update: The above code requires that you know the name of the exported object, which is not always known. Some modules may export multiple variables too. For example, jQuery exports both $ and jQuery. The following fiddle illustrates a technique for solving this issue by copying any global objects which did not exist before the script was loaded:
https://jsfiddle.net/qu0pxesd/3/
Which approach would work best really depends on the specific needs of the project. Context would determine which one I'd use.
Undefining define Temporarily
I'm mentioning it because you tried it.
DON'T DO THIS!
The approach of undefining define before you load your script and restoring it after is not safe. In the general case, it is possible for other code on the page to perform a require call that will resolve after you've undefined define and before you've defined it again. After you do document.body.appendChild(s); you're handing back control to the JavaScript engine, which is free to immediately execute scripts that were required earlier. If the scripts are AMD module, they'll either bomb or install themselves incorrectly.
Wrapping the Script
As Dheeraj V.S. suggests, you can wrap the script to make define locally undefined:
(function(define) { /* original module code */ }())
can work for trivial cases like the one you show in your question. However, cases where the script you try to load actually has dependencies on other libraries can cause issues when it comes to dealing with the dependencies. Some examples:
The page loads jQuery 2.x but the script you are trying to load depends on a feature added in jQuery 3.x. Or the page loads Lodash 2 but the script needs Lodash 4, or vice-versa. (There are huge differences between Lodash 2 and 4.)
The script needs a library that is not otherwise loaded by something else. So now you are responsible for producing the machinery that will load the library.
Using RequireJS Contexts
RequireJS is capable of isolating multiple configurations from one another by defining a new context. Your bookmarklet could define a new context that configures enough paths for the script you are trying to load to load itself and its dependencies:
var myRequire = require.config({
// Within the scope of the page, the context name must be unique to
// your bookmarklet.
context: "Web Designer's Awesome Bookmarklet",
paths: {
myScript: "https://...",
jquery: "https://code.jquery.com/jquery-3.2.1.min.js",
},
map: {...},
// Whatever else you may want.
});
myRequire(["myScript"]);
When you use contexts like this, you want to save the return value of require.config because it is a require call that uses your context.
Creating a Bundle with Webpack
(Or you could use Browserify or some other bundler. I'm more familiar with Webpack.)
You could use Webpack to consume all the AMD modules necessary for the script you are trying to load to produce a bundle that exports its "module" as a global. At a minimum, you'll need something like this in your configuration:
// Tell Webpack what module constitutes the entry into the bundle.
entry: "./MyScript.js",
output: {
// This is the name under which it will be available.
library: "MyLibrary",
// Tell Webpack to make it globally available.
libraryTarget: "global",
// The final bundle will be ./some_directory/MyLibrary.js
path: "./some_directory/",
filename: "MyLibrary.js",
}
Once this is done, the bookmarklet only needs to insert a new script element that points to the produced bundle and no longer has to worry about wrapping anything or dealing with dependencies.
If it were me, I would have the url provide the hint as to how to load the module. Instead of having just a "scripts/" directory -> I would make "scripts/amd/", "scripts/require/", etc. Then query the url for "amd", "require", etc. within your loadScript method... using, e.g.,
if (url.includes('amd')) {
// do something
} else if (url.includes('require')) {
// do something different
}
That should let you avoid the global var entirely. It might also provide a better structure for your app in general.
You could also return an object with a script property and loadType property that specifies amd, require, etc... but imho the first option would be the quickest and save you some additional typing.
Cheers

Call of anonymous function (vanilla js, pure js, no jquery)

I want to grab browser version and OS from useragent via js - this is a practicing example (cause yes - I know - feature detection is how you do it properly ;).
I stumbled across a little library that does so and now I'm trying to understand whats going on.
See this Codepen: http://codepen.io/anon/pen/gPWZGE?editors=001
obviously by running bowser.version - you get the version extracted from the browsers useragent. However the function bowser itself is an anonymous function - even though I can access elements within this function which has to do with this part of the code
!function (name, definition) {
var module;
var define;
if (typeof module != 'undefined' && module.exports) module.exports = definition();
else if (typeof define == 'function' && define.amd) define(definition);
else this[name] = definition();
}
To be honest I have hardly any idea whats going on there - can someone please explain what those lines are doing?
Thanks a lot
Ok, so step by step ...
first you have an IIFE
!function( name, definition ) {
// some code
}( 'bowser', function(){
// your module code
});
this calls the first function immediately with two parameters: the name of the module (here "bowser") and the actual definition function. In that definition function you create the module: You define any objects, properties and whatever else it needs to work. Important point is: You interface (the view the "outside" has on your module, has to be returned by that defintion function (in your codepen this is line 282: return bowser;).
The outer function then determines, how to publish your module:
First it looks for a CommonJS environment:
typeof module != 'undefined' && module.exports
This would be used, e.g., inside NodeJS. If it found the respective objects, it will export your module as a CommonJS module: module.exports = definition().
If this was not a CommonJS environment, the script looks, if it is an AMD (for example, requireJS):
typeof define == 'function' && define.amd
Again, if that matches, the module is exported as a AMD module: define(definition).
Finally, if this is neither CommonJS nor AMD, the script assumes it is a (vanilla) browser or something similar and just attaches the module to the global namespace:
this[name] = definition();
Note, that each time the definition function is called and the return value is exported as a module. This is why your definition function has to return the module interface.

How do you manage namespace in Meteor?

So here is my problem :
Currently, I have a dozen of functions related to WEBRTC within a template js file. My objective is to have those functions in a separate file, called webRTCWrapper.js for example, and to call those functions in my template without using global variable.
I think I must use namespaces, am I correct ?
If so, how do you use them ?
EDIT : For anyone interested, this is exactly what I was looking for :
http://themeteorchef.com/snippets/using-the-module-pattern-with-meteor/
Make a directory called packages/ parallel to your .meteor/ directory. You can create a package that exports a single object/function. On the command line, use meteor create --package <yourpackagename> and meteor add <yourpackagename> You can edit the js file to add a namespace.
MyNamespace = {};
MyNamespace.myFunction = function () { };
Then, in the package.js, simply export that namespace.
api.export('MyNamespace');
You can use a common pattern of having a global object and your functions inside that object.
Greetings = {
hello: function(name) { return "Hello "+name+" how are you?"; }
}
And then you can call it inside the template helpers :
Template.GreetingsTemplate.helpers({
sayHello: function() { return Greetings.hello('Maxence'); }
})
Take note of the loading order of files in Meteor, anything inside the lib folders is loaded first. If you run into problems where "Greetings" object is not defined, then its because that file was not loaded already.
Edit:
You can reuse the same pattern for adding more functions in different files (you could use App = App || {} but it will throw error in Chrome for example).
App = (typeof App === 'undefined')? {} : App;
App.someFunction = function(){};
or even, if you use underscore.js:
App = (typeof App === 'undefined')? {} : App;
_.extend(App, {
someFunction: function(){}
});
Since now the regular way to use the code from another file was going through a global (server and client). As Joao suggested you can make your own global App variable where you will store or more generically a global MODULE one (basically the same solution as Joao but with explanation).
But with with the arrival of ES2015 support, we will very soon be able to have an official pattern to achieve this. However as the 1.2 does not supports yet the import/export syntax:
Note, The ES2015 module syntax (import/export) is not supported yet in Meteor 1.2.
If you want to start using those features earlier, I would recommend using this package which is an temporary solution to fill the current import/export gap, the meteor team development are currently looking for an elegant solution to support this.

requireJS optional dependency

I'm adding AMD support to a javascript library I develop.
This library may use jquery but it will still work if jquery isn't loaded.
When defining the module dependency there's a way to set a dependency as 'optional' so that if that library is missing the module will still work?
I've had exactly the same problem recently, and here's how I fixed it. I defined a RequireJS plugin called optional which ignores modules that fail to load by explicitly defining them as an empty object (but I suppose you could also define it as null or anything else if you wanted).
Here is the code (tested with RequireJS 2.1.15):
define("optional", [], {
load : function (moduleName, parentRequire, onload, config){
var onLoadSuccess = function(moduleInstance){
// Module successfully loaded, call the onload callback so that
// requirejs can work its internal magic.
onload(moduleInstance);
}
var onLoadFailure = function(err){
// optional module failed to load.
var failedId = err.requireModules && err.requireModules[0];
console.warn("Could not load optional module: " + failedId);
// Undefine the module to cleanup internal stuff in requireJS
requirejs.undef(failedId);
// Now define the module instance as a simple empty object
// (NOTE: you can return any other value you want here)
define(failedId, [], function(){return {};});
// Now require the module make sure that requireJS thinks
// that is it loaded. Since we've just defined it, requirejs
// will not attempt to download any more script files and
// will just call the onLoadSuccess handler immediately
parentRequire([failedId], onLoadSuccess);
}
parentRequire([moduleName], onLoadSuccess, onLoadFailure);
}
});
You can then require a module optionally using simply
require(['optional!jquery'], function(jquery){...});
knowing that if the jquery module could not be loaded, the parameter passed to your callback function will be an empty object.
You cannot really set it optional, but you can catch the error and unload the module using undef:
require(['jquery'], function ($) {
//Do something with $ here
}, function (err) {
//The errback, error callback
//The error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
if (failedId === 'jquery') {
//undef is function only on the global requirejs object.
//Use it to clear internal knowledge of jQuery. Any modules
//that were dependent on jQuery and in the middle of loading
//will not be loaded yet, they will wait until a valid jQuery
//does load.
requirejs.undef(failedId);
...
}
});
Full example here.
A plugin is not needed for this.
This can be done without plugins for RequireJS. In addition, you can do this for UMD modules as well.
Using jQuery only when it's already loaded
It's actually pretty simple, using require.defined, which lets you test whether a module has already been loaded or not. If so, you require jQuery and use it, otherwise you just skip the optional part:
define(['require'], function(require){
if (require.defined('jquery') {
var $ = require('jquery');
$.fn.something = function(){};
}
});
Notice how we add 'require' as a dependency, so we get a local require function that has the defined method on it.
Also note that this code will only find jQuery if it has been loaded prior to this module. If some module loads jQuery after this module has already loaded then it will not pickup jQuery afterwards.
This will not attempt to load jQuery and hence will not cause an error message in the logs.
Bonus: UMD support
If you want your library to support AMD loaders (RequireJS), CommonJS (Node) and regular script tags, and have an optional dependency on jQuery, here is what you can do:
(function(u, m, d) {
if ((typeof define == 'object') && (define.amd)) {
// handle AMD loaders such as RequireJS
define(m, ['require'], d);
}
else if (typeof exports === 'object') {
// handle CommonJS here... Does not really make sense for jQuery but
// generally, you can check whether a dependency is already loaded
// just like in RequireJS:
var $ = null;
try {
require.resolve('jquery'));
// the dependency is already loaded, so we can safely require it
$ = require('jquery');
} catch(noJquery) {}
module.exports = d(null, $);
}
else {
// regular script tags. $ will be available globally if it's loaded
var $ = typeof jQuery == 'function' ? jQuery : null;
u[m] = d(null, $);
}
})(this, 'mymodule', function(require, $) {
// if `$` is set, we have jQuery... if not, but `require` is set, we may
// still get it here for AMD loaders
if (!$ && require && require.defined && require.defined('jquery')) {
$ = require('jquery');
}
// At this point, `$` either points to jQuery, or is null.
});
Further reading
Node.js - check if module is installed without actually requiring it
https://github.com/jrburke/requirejs/issues/856

Categories

Resources