How can I make requirejs polymorphically load a module? - javascript

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;
}

Related

How can I access a service in config function or access a provider inside a service

I need to configure angular bootstrap tooltip (uibTooltip) to be disabled for mobile devices when using angular-bowser for device detection.
This could be done simply by:
isMobile = _bowser.mobile
$uibTooltipProvider.options = { trigger: isMobile ? "none" : "mouseenter" }
Problem: $uibTooltipProvider is a provider and bowser is a service.
I have to use $uibTooltipProvider in a config function while I can't use bowser service in a config function. And neither I can use $uibTooltipProvider in a run function where I can use bowser
I have already tried overriding the $get function as they suggest here but the "ontouchstart" event in $window does not apply for tablets where I want to keep tooltips enabled.
Is there any way I can get a workaround this?
I finally decided to inject a small css modification in run time. I had the three following options:
1. Use that hack on GitHub: I did not like the fact that in order to make it work I had to empirically (by placing a breaking point) find out which were the actual services injected in the $get function of the uibTooltipProvider. (see there is a difference between the services injected in the github thread and the ones I had to inject (see code snippet)
2. Add the bowser library and use it statically: I did not like this option because we are already using angular-bowser as a dependency for our DeviceDetector service, so we would be using the same library twice: one statically to configure the tooltip options and one in runtime for everything else.
3. Inject a small css modification (the option I chose):
public disableTooltipsForTouchScreen(): void {
if ( this._deviceDetector.isMobile() || this._deviceDetector.isTablet() ) {
let styleSheet = document.createElement("style");
styleSheet["innerHTML"] = ".tooltip { display: none; }";
document.body.appendChild(styleSheet);
}
}
And if we ever need to have a finest control over the bootstrap-tooltip configuration then I will consider option 2.

Angular2 with Typescript, how to use Plupload CDN script file in lazy-loaded module?

I would like to use Plupload in an Angular2 component and access the Plupload JavaScript file from a CDN. I want it specific to a component so that it is not downloaded if it is not required - I want it to be in a lazy loaded module. How can I do this?
Now fully answered on this page!
The result of this quest, which included offering and awarding bounties to two people who worked hard with me on it, is as follows:
Example of using Plupload with Angular 2 and TypeScript
How to Lazy load a script from a CDN in Angular 2
Example of how to use Plupload in a lazy loaded module
How to use a lazy loaded script in Angular 2
(See edit history for the ugly details that used to make up this question.)
Here's the overview of what you need to do to create a lazy-loaded Plupload feature while loading Plupload from a CDN:
When the feature is needed (e.g. user clicks a button or visits a page), dynamically add a <script> tag to the page to load the Plupload library from a CDN.
Wait until the library is loaded to proceed (or you could get a "plupload is undefined" error).
Display the UI to interact with Plupload in one of your Angular templates. In its simplest form, this UI consists of two buttons: "Select files" and "Upload files".
Initialize Plupload and wire it up to the UI.
Complete, working code: https://plnkr.co/edit/4t39Rod4YNAOrHmZdxqc?p=preview
Please take note of the following points in my implementation:
Regarding #2. A better way to check whether Plupload has finished loading would be to poll the global namespace for the existence of the plupload variable. As long as window.plupload does not exist, it means the library hasn't been loaded yet and that we should NOT proceed. For simplicity my code just waits for one second and proceeds.
Number 4 can prove a bit tricky. Plupload makes a heavy use of direct DOM access to wire its API to the HTML (e.g. document.getElementById('filelist')). This is something Angular discourages and that you should try avoiding whenever possible. More specifically direct DOM access is used in the following places:
To tell Plupload which DOM element should trigger the "Select files" dialog (what they call the browse_button config option). For this I could not avoid the direct DOM reference and I used the #ViewChild decorator to get a hold of the "Select Files" button.
To display selected files in the template. For this I converted the Plupload syntax into the regular Angular syntax. I push selected files to a class property called fileList which I display in the template using a standard *ngFor.
The "Upload Files" button triggers some code that does the actual uploading and refreshes the UI to show upload progress. Once more, I converted this to regular Angular syntax using event binding and data binding.
Let me know if you have any questions.
In this approach no need for any extra loader modules.
See example (check console for Woohoo): http://plnkr.co/edit/gfUs4Uhe8kMGzPxwpBay?p=preview
updated plunker: https://plnkr.co/edit/leG062tg7uX8sLrA0i2i?p=preview
You can lazyload some js by adding the script url to you document:
Create a my-lazy-load.function.ts:
export function lazyload(url) {
// based on https://friendlybit.com/js/lazy-loading-asyncronous-javascript/
let scripts = document.getElementsByTagName('script');
for (let i = scripts.length; i--;) {
if (scripts[i].src.match(url)) return true;
}
let s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = url;
let x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
return true;
}
In your component that you want to add plupload:
import {lazyload} from "./my-lazy-load.function.ts";
export class MyComponent implements OnInit {
pluploadInterval:number = null;
hasPlupload: boolean = false;
ngOnInit() {
lazyload("https://cdnjs.cloudflare.com/ajax/libs/plupload/2.3.1/plupload.full.min.js");
this.pluploadInterval = window.setInterval(()=>{
if(window.plupload) { // you can check existence of plupload object any way you want
// Woohoo, I am ready to be used
this.hasPlupload = true; // everything is run outside ngZone, wrap it if angular2 is not reacting to changes, or change window.setInterval to setInterval
window.clearInterval(this.pluploadInterval); // don't forget to clean interval
}
}, 100); // timeinterval can vary
....
The browser will load this automatically.
Notice if(plupload) it assumes that there is global object plupload that the script adds (I do not know if it truely added, check your working example in pure javascript). As it is jquery extension you can check it's prototype like this: jQuery test for whether an object has a method?
OLD HISTORICAL:
#Reid here is plunker: https://plnkr.co/edit/zDWWQbTQUSHBqCsrUMUi?p=preview the plupload is actually loaded, but added to require with define("plupload", ['./moxie'], extract); I am not sure at the moment how to extract from there and which package require is belong to... the code for finding correct module loader belongs to plupload itself, here it is (from plupload.dev.js):
if (typeof define === "function" && define.amd) {
define("plupload", ['./moxie'], extract);
} else if (typeof module === "object" && module.exports) {
module.exports = extract(require('./moxie'));
} else {
global.plupload = extract(global.moxie);
}
I think that your best bet is to use the Require.js Library so that you can dynamically load your scripts from within your components.
The small trade off is that you will have to add this 18KB library to your index.html page (CDN), however this could save you huge amounts of loading if your 3rd party libraries are massive.
I have no experience with using plupload, so instead I put together the following plunkr which uses an external animation library, drawn from a CDN. The plunkr animates a number from 0 - 100.
https://plnkr.co/edit/fJCtezsERYHOYplLh7Jo?p=preview
index.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.3/require.min.js"></script>
component.ts
ngOnInit(){
// Dynamically loads the framework from the CDN.
require(["https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenLite.min.js"],
// This function is then called ONCE LOAD IS COMPLETE
function (common) {
// Do greensock things
var demo = {score:0},
scoreDisplay = document.getElementById("scoreDisplay");
//create a tween that changes the value of the score property of the demo object from 0 to 100 over the course of 20 seconds.
var tween = TweenLite.to(demo, 20, {score:100, onUpdate:showScore})
//each time the tween updates this function will be called.
function showScore() {
scoreDisplay.innerHTML = demo.score.toFixed(2);
}
});
}
What I like about this approach, is that in the onLoad callback from require, the syntax is unchanged from a normal implementation of the library, so you can just copy paste your currently working code into the callback.

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).

Cleaning up a large piece of Javascript code

I am writing an application in Titanium for Android. I have a lot of code in a single JS file. I would like to know if there is any function like php's include to break the code into multiple files and then just include them.
Thanks
Use the CommonJS / RequireJS approach, specifically the require command. This is the (strongly) recommended way for dealing with large systems in Titanium, and is well documented on their website, along with many best practices for dealing with JavaScript modularization specific to Titanium. Here is the documentation from Titanium on this.
For example, to create a module that encapsulates a 'view' of some kind, put it in a file named MyCustomView.js with this content:
// MyCustomView.js
function MyCustomView(message) {
var self = Ti.UI.createView({
backgroundColor : 'red'
});
var label = Ti.UI.createLabel({
text : message,
top : 15,
.... // Other initialization
});
// ... Other initialization for your custom view, event listeners etc.
return self;
}
module.exports = MyCustomView;
Now you can easily use this module in another class, lets assume you put this in your /Resources folder, lets load the module inside app.js and add it to the main window.
// app.js
var MyCustomView = require('MyCustomView');
var myView = new MyCustomView('A message!');
Titanium.UI.currentWindow.add(myView);
You can use this approach to make custom views, libraries of reusable code, and anything else you would like. Another common thing would be to have a Utility class that has many different helper functions:
// Utility.js
exports.cleanString = function(string) {
// Replace evil characters
var ret = string.replace(/[|&;$%#"<>()+,]/g, "");
// replace double single quotes
return ret.replace(/"/g, "''");
}
This method can be easily used like this:
// app.js
var Utility = require('Utility.js');
Ti.API.info(Utility.cleanString('He##o W&orld$'));
Another common method I use it for is to implement the Singleton pattern as each module loaded is its own functional context, so if you like, you can have values that persist:
// ManagerSingleton.js
var SpriteManager = {
count : 0
};
exports.addSprite = function() {
SpriteManager.count++;
}
exports.removeSprite = function() {
SpriteManager.count--;
}
You would load and use this much the same way as Utility:
// app.js
var ManagerSingleton = require('ManagerSingleton');
ManagerSingleton.addSprite();
This is something of a more elegant solution instead of using global variables. These methods are by no means perfect, but they have saved me a lot of time and frustration, and added depth, elegance, and readability to my Titanium code for Apps of all sizes and types.
There are two dominant module systems in the Javascript world.
One is the CommonJS and the second is AMD. There is a lot of discussion about which one is best and for what purpose. CommonJS is more widely used for server side JS while AMD is used mostly for browser JS.
RequireJS (requirejs.org) seems to be the most popular AMD system.
For information on JS module systems please read here: http://addyosmani.com/writing-modular-js/

Building a remote jQuery UI widget with requirejs dependencies

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 :).

Categories