I've a script tag (type="module"), which is dynamically populated with import statements by PHP based on some parameters. The populating process is controlled by the authorization system of the site, and it can drop some imports from the script, depending on the rights of the current user.
An example of the final rendered script tag on a page:
<script type="module">
import ui from 'path_to_ui_module';
import read from 'path_to_read_module';
import edit from 'path_to_edit_module'; // Could be omitted by the authorization
import create from 'path_to_create_module'; // Could be omitted by the authorization
import delete from 'path_to_delete_module'; // Could be omitted by the authorization
ui.init();
</script>
All the imports are guaranteed to be objects only.
Now, the problem lies in ui.init method, where I want to assign some references of some properties of the loaded modules (and their submodules) to a property in ui, but I don't know, which modules are actually loaded, because the authorization system may have dropped some of the modules from the script. For example, all the loaded modules have a cmd object containing various methods, and I'd like to assign a reference to those methods to ui.cmd object. How can I iterate through all the namespaces of the imported modules, including the possible submodules imported by the modules loaded in the script?
Currently I've modified the authorization system to create the ui.init call, which passes the references of the really loaded modules to ui.init method, but the submodules are naturally not included.
ui.init({modules: {read: read, edit: edit, create: create, delete: delete});
(The modules dropped by the authorization system are not included in modules object.)
With the passed modules object, a simplified ui.init method looks like this ("main module" in the comments is ui, "submodule" refers to the modules passed via the arguments):
init (options) {
for (const module in options.modules) {
this[module] = options.modules[module]; // Add submodule reference to the main object
this[module].main = this; // Add main module reference to submodule
Object.assign(this.cmd, this[module].cmd); // Assign the cmd methods of the submodule to the main module's cmd object
this[module].init(); // Initialize the submodule
}
}
Reformed question: Is there a native "list" of all imported modules, including the submodules the imported modules have imported, available, that I can use instead of options.modules in the for .. in loop? Or alternatively, is there a way for the submodules to somehow "expose" themselves after they've been loaded, so that they can create a list of the loaded modules?
You can try using dynamic imports:
(I am assuming that your PHP script can dynamically generate the .then callback)
<html>
<body>
<script type="module">
Promise
.all
( [ import('./node_modules/the-answer/dist/the-answer.es.js')
, import('./node_modules/the-answer/dist/the-answer.es.js')
, import('./node_modules/the-answer/dist/the-answer.es.js')
]
)
.then(([{default: answer1}, {default: answer2}, {default: answer3}]) => {
// You now know all the modules that have loaded 😊
console.log(answer1, answer2, answer3);
});
</script>
</body>
</html>
Because the dynamic form of import returns a promise, you can wait for all of them to resolve and have access to them.
The the-answer module is literally this:
export default 42;
Which is why I need to destructure each module in the .then() method, e.g.
.then(([{default: answer1}]) => alert(answer1))
Related
I have a file created on a custom route that I will use for local collections.
This file is under imports/localDb/ and is named patients.js:
import { Mongo } from 'meteor/mongo';
const PatientsLocal = new Mongo.Collection();
export default PatientsLocal;
What I'am doing is to import this file everytime that I need to do an operation (Insert, fetch, ...)
So for example, I have in a file:
import PatientsLocal from '../../../localDb/patients';
// ...
PatientsLocal.insert(patient);
The issue is :
When I have to do an operation I import the patients.js file, so the file runs entirely again and the collection is instantiated another time, so I can't insert an object in one file and fetch it on another.
What can I do to persist the collection during the runtime to achieve what I need?
You should be fine by exporting it as const:
export const PatientsLocal = new Mongo.Collection();
However, there is more to think about: The module contains both definitions and instantiation code. A good practice is to decouple instantiation from definition.
A possible workaround would be to export a sort of context Object that contains several static properties that define the overall Patients context and decorate it on startup with properties that have to be instantiated:
imports/local/patients.js
export const Patients = {
name: 'patients',
collection: null,
// other definitions...
}
Then in your startup code you instantiate the Collection once:
client/main.js
import { Patients } from '../imports/local/patients'
Patients.collection = new Mongo.Collection()
(note that this could be moved into an own startup module file)
and in your runtime code you import the context instead of the collection:
import { Patients } from '../../../local/patients'
// ...
Patients.collection.insert(patient)
Note that these are just a few examples of how to approach this problem. Others may include using the global namespace (which is discouraged but not prohibited) or some sort of registry class that keeps track of all the instances of the collections (like dburles:mongo-collection-instances which is a great package but accesses collections via name which is not defined on local collections).
I have an external js file with a bunch of jQuery methods for the user interface. However, the Angular router is asynchronous, so user interface elements on other pages do not get properly rendered by the script, since they don't yet exist in the DOM.
Formerly, I was cherry picking some methods from the script and adding them to a method called rerender, but this is becoming cluttered.
My question was to see if there is a way to load and execute the entire script again inside of the rerender method?
You can import external scripts. Do it inside the constructor:
constructor() {
System.import('script-loader!bower_components/fullcalendar/dist/fullcalendar.min.js').then(()=>{
this.render(); // Do whatever, for demonstration I call a render() method
})
}
Make sure in your custom-typings you have declared System:
declare let System: SystemJS;
interface SystemJS {
import: (path?: string) => Promise<any>;
}
interface GlobalEnvironment {
SystemJS: SystemJS;
System: SystemJS;
}
Option 2:
Alternatively, you can put this into your module after the import statements:
require('bootstrap/js/tooltip.js'); // For example
About the framework (while I think the problem itself does not heavily rely on that): Angular 2 with Webpack
There is the library Leaflet.heat which relies on simpleheat. I got the missing type definitions under control.
I'm importing the libraries in my vendor.ts
[...]
import 'simpleheat';
import 'leaflet.heat/src/HeatLayer';
[...]
Inside of the HeatLayer class, the function simpleheat:
[simpleheat.js]
if (typeof module !== 'undefined') module.exports = simpleheat;
function simpleheat(canvas) {
...
is called. However, the HeatLayer module file does not require simpleheat inside it's file.
Thus, creating an instance of L.HeatLayer works, but the execution of the respective code in it's function fails with
ReferenceError: simpleheat is not defined
Now, adding (for testing purposes) simpleheat = require('simpleheat'); into the HeatLayer file (a vendor), it works.
Understandably, I don't want to modify a vendor file.
Question:
What options do I have, to make the function simpleheat accessible from inside the HeatLayer module?
One Solution I just found:
Change the vendor.ts to the following:
(<any>window).simpleheat = require('simpleheat');
import 'leaflet.heat/src/HeatLayer';
Are there others/better?
I am currently developing a web application where we are using the Model View Controller method for organizing our information and code. In our case we are combining the View and Controller into one Javascript file and the Model is separate.
My question comes here. I've got prototype objects in my model, but I want to instantiate instances of these objects in my viewcontroller Javascript file. How do I get them talking to each other?
There are some ways to achieve that. Today, the simplest one would be:
<script src='model.js'></script>
<script src='view-controller.js'></script>
So, since your model.js will be loaded first, you can use it inside the view/controller.
Another way is by using modules. The most used today is RequireJS. E.g:
require(['model'], function(model) {
// This function is called when model.js is loaded.
// If model.js calls define(), then this function is not fired until
// model's dependencies have loaded, and the model argument will hold
// the module value for model.js.
});
ECMAScript 6 (the next version of Javascript), will have native modules, so you'll be able to do:
import * as model from './model'
var x = model.variable; // etc
You might also want to look into using Browserify if you are familiar with Node and RequireJS as you an also use NPM modules in the front-end.
http://browserify.org/
Browserify allows you to export your JS code from one file and require it in another (simplified idea).
file 1: myfunc.js
var myFunc = function(){
console.log("I'm an exported function that's in another file");
};
module.exports = myFunc;
file 2: app.js
var myFunc = require('./myfunc.js');
myFunc(): // logs "I'm an exported function that's in another file"
I'm trying to include IOUtil.js and ChannelReplacement.js in my add-on, using the Cu.import(...) function. These two both use xpcom_generateQI, which I'm trying to obtain from the XPCOM jsm, but the two scripts cant access it.
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const xpcom_generateQI = XPCOMUtils.generateQI;
Cu.import(self.data.url("IOUtil.js"));
Cu.import(self.data.url("ChannelReplacement.js"));
gives me xpcom_generateQI is not defined.
How do I access a function which is defined in main.js?
Issues
Don't use Cu.import for local SDK modules. Don't write JS code modules for SDK add-ons, the SDK uses CommonJS-style modules together with the require() facility which also comes with proper cleanup for free, which cannot be said for JS code modules and Cu.import (you'd need to properly Cu.unload everything and likely kill some references yourself).
That https-everywhere stuff are neither JS code modules nor SDK modules, but uses the subscript loader. Either convert it to SDK code modules, or use the subscript loader yourself.
It is OK to import built-in JS Code modules in different scopes/modules. There is not actually a need to make available xpcom_generateQI from main (although it can be done; well, get to that).
To be future proof, you should bind your xpcom_generateQI shortcut properly, as in XPCOMUtils.generateQI.bind(XPCOMUtils). Otherwise, if the implementation changes and requires a proper this, your stuff will break.
To export something from any CommonJS module, you need to put it into the exports module. See the first link.
To import something, use require() (first link again).
Be aware of circular references, where Module A imports Module B imports Module A. Right now this kinda works (but only kinda, because some stuff might not be available from Module A when Module B imports it like this, as Module A is not fully loaded). Better avoid it.
Example 1 (circular)
So here is a example with circular require (main imports modules imports main)
main.js
function someFunction() {
console.log("some function called");
}
exports.someFunction = someFunction;
var mod = require("./module");
mod.anotherFunction();
module.js
const { someFunction } = require("./main");
exports.anotherFunction = function() {
someFunction();
}
Now, because of circular references this is a fragile construct. If works right now, but when modules get more complex or the SDK changes, it might break... Better put someFunction into a third module.
Example 2 (avoiding circular imports)
main.js
var mod = require("./module");
mod.anotherFunction();
// Or call someFunction directly
var { someFunction } = require("./utils");
someFunction();
module.js
const { someFunction } = require("./utils");
exports.anotherFunction = function() {
someFunction();
}
utils.js
function someFunction() {
console.log("some function called");
}
exports.someFunction = someFunction;
There are no circles anymore. If you wanted to reuse xpcom_generateQI, you'd put it as a property of exports in utils.js (in this example) and later require it with require("./utils").
https-everywhere
The https-everywhere stuff needs to be either converted or loaded using the subscript loader. I would recommend against the subscript loader, because in all likelihood the verbatim https-everywhere code does not clean up after itself. I'd actually also recommend against just converting it by throwing some stuff in (exports.xzy = ...). This code is not meant to be run in the SDK. Better create your own implementation and just borrow ideas from https-everywhere where needed.