Ensure requireJS conditional module loaded - javascript

I'm trying to use requireJS in an existing project. I have an App module that has information about current locale, url ... and want load another module if current locale is something special:
// App.js
define([], function() {
return {
setEnv: function(obj) { //obj: {locale:'en'}
this.env = obj;
that = this;
if( this.env.locale == 'fa' )
require(['locale/fa'], function(fa) {
that.number = fa.number
};
else
this.number = function( n ) { return n+'' }
}
}
});
Locale files are like:
// locale/fa.js
define([], function() {
var enToFaDigit(n) { // a function converts a number to persian representation };
// more stuff
return {
number: enToFaDigit
};
});
Now the problem is i don't know when App module loaded number method. If i was sure locale/fa module should load at some point, i could wrap it using require(['locale/fa'] or use shim. Currently I'm using old blocking way to load appropriate locale/*.js and PHP to choose right file, without problem. But i want know how requireJS programmers write similar code. Probably there is some way better than this code:
require(['app'], function(App) {
if(App.env.locale == 'fa') {
require(['locale/fa'], function(fa) {
// 8-S
}
}
});

This sounds like a case for the i18n plugin! I believe you are allowed to define functions as well as strings. I would define a bundle like so:
//nls/formatters.js
define({
root: {
currency: function(num) {
// implementation for default currency format (en-US often)
}
},
"fa": true
})
And here's your Persian override:
//nls/fa/formatters.js
define({
currency: function(num) {
// implementation for farsi currency format
}
})
In your app.js file:
require(['app','i18n!nls/formatters'], function(App, Formatters) {
Formatters.currency(5.00); // works!
});

Related

JHipster Blueprint - Generate files with a specific path using templates | Get default java package name

EDIT : the former question was "JHipster Blueprint - How to get default Java package name ?"
I am developing a blueprint with JHipster that overrides the entity-server sub-generator. The desired behaviour is to replace all files in /src/main/java/defaultpackageName/domain/ from the project generated by the blueprint with my generated files. This is my code (files.js):
const entityServerFiles = {
noHibernate: [
//domain files
{
path: 'src/main/java/XXX/domain/',
templates: [
{
file: 'Entity.java',
renameTo: generator => `${generator.persistClass}.java`
}
]
}
]
};
function writeFiles() {
return {
write() {
this.writeFilesToDisk(entityServerFiles, this, false);
}
}
}
module.exports = {
writeFiles
};
For now it just creates a folder XXX in /src/main/java/ with my generated files in it.
What would I need to write in the XXX in path: 'src/main/java/XXX/domain/' in order to generate the files at the right place?
I did some digging on github on the generator-jhipster project and the prompt asking the user for the default java package name is in /generator-jhipster/generators/java/index.cjs/. This is the whole code https://github.com/jhipster/generator-jhipster/blob/main/generators/java/index.cjs
But I just took the important part:
const {
PACKAGE_NAME,
PACKAGE_NAME_DEFAULT_VALUE,
PRETTIER_JAVA_INDENT,
PRETTIER_JAVA_INDENT_DEFAULT_VALUE,
BUILD_TOOL,
BUILD_TOOL_DEFAULT_VALUE,
BUILD_TOOL_PROMPT_CHOICES,
} = require('./constants.cjs');
get prompting() {
return {
async showPrompts() {
if (this.shouldSkipPrompts()) return;
await this.prompt(
[
{
name: PACKAGE_NAME,
type: 'input',
validate: input => this.validatePackageName(input),
message: 'What is your default Java package name?',
default: () => this.sharedData.getConfigDefaultValue(PACKAGE_NAME, PACKAGE_NAME_DEFAULT_VALUE),
},
],
this.config
);
},
};
}
From what I understand, I just need to access the PACKAGE_NAME constant from my blueprint and it should work. Any ideas?
I just found the solution...
const entityServerFiles = {
noHibernate: [
//domain files
{
path: 'src/main/java/',
templates: [
{
file: 'package/domain/Entity.java',
renameTo: generator => `${generator.entityAbsoluteFolder}/domain/${generator.persistClass}.java`
}
]
}
]
};
function writeFiles() {
return {
write() {
this.writeFilesToDisk(entityServerFiles, this, false);
}
}
}
module.exports = {
writeFiles
};
The path property specifies the path inside the templates folder. Meanwhile, you can specify the path you want your files to be generated inside the project in the renameTo property.
So the answer to my question is ${generator.entityAbsoluteFolder} which had nothing to do with my original hypothesis, but this question can also be useful for writing templates in general.

How to lazy load a js file in React (for a multilingual app)

I would like to create a multilingual app with React.
The way I see it would be to have a js file for each language, for example :
en.js:
module.exports = {
langEnglish: 'English',
langFrench: 'French',
navHome: 'Home',
navUsers: 'Users',
...
};
fr.js:
module.exports = {
langEnglish: 'Anglais',
langFrench: 'Français',
navHome: 'Accueil',
navUsers: 'Utilisateurs',
...
};
As each language file will be quite big and there could be dozens of different languages supported, I would prefer to download only the correct file to use depending on the language chosen in order to minimize loading time (and bandwidth usage).
For example I could have a variable in the app state
var App = React.createClass({
getInitialState: function () {
return {
lang: 'en'
};
},
...
and some user control to switch this variable between fr and en.
Is it possible to load only the en.js file on the initial load, and if the user switches the language to French then load and use the fr.js file instead and so on for each language?
Make use of some advanced webpack features, such as code splitting. You can use webpacks require.ensure for async loading your files.
Create a file:
i18n.js
var currentTranslation = {};
module.exports = {
getTranslation: function() {
return currentTranslation;
},
loadI18n: function(region, cb) {
switch (region) {
case 'en':
require.ensure([], function(require) {
cb(currentTranslation = require('./en'));
}, 'i18n-en'); // will create a chunk named 'i18n-en'
break;
case 'fr':
require.ensure([], function(require) {
cb(currentTranslation = require('./fr'));
}, 'i18n-fr'); // will create a chunk named 'i18n-fr'
break;
default:
require.ensure([], function(require) {
cb(currentTranslation = require('./en'));
}, 'i18n-en');
}
}
}
App.js
var i18n = require('./i18n');
and when you need the translation strings to be loaded async
you can call:
i18n.loadI18n('en', function(texts) {
console.log(texts);
});
once webpack loads that chunk, you will be able to get the translation texts using the function
var texts = i18n.getTranslation(); // call this from anywhere and it will return english texts
if you want to switch language, just call
i18n.loadI18n('fr', function(texts) {
console.log(texts);
});
var texts = i18n.getTranslation(); // will return french texts

Reloading the backbone view and i18n translation file in Backbone Application?

I am using i18n.js in BackboneJS application for Text localization. but stuck at a point where i need reload the text translation on language change in the application. i am calling setting view render() function on language change but doesn't work for me, but on reloading index.html it works. So How can i reload the translation file and view to reflect the changes. below is what i tried--
application-bootstrap.js
var locale = {};
locale.language = "en-us";//default
locale.setLanguage = function (language) {
localStorage.setItem('localLanguage', '' + language);
};
locale.getLanguage = function () {
return localStorage.getItem('localLanguage');
};
require.config({
config: {
i18n: {
locale: locale.getLanguage()
}
}
});
settingView.js
define(['compiled/settingTmpl','i18n!nls/setting'
], function (SettingTmpl,setting) {
'use strict';
var SettingView = Backbone.View.extend({
events: {
"change #languageSelect": "changeLocale"
},
initialize: function () {
WellNation.helper.log("Setting view initialize");
this.render();
},
changeLocale: function (e) {
var locale = e.currentTarget.value;
WellNation.locale.setLanguage(locale);
this.render();
},
render: function () {
this.$el.html(SettingTmpl({speak:setting}));
return this;
}
});
return SettingView;
});
settingTmpl.handlebars
<div class="row">
<label>{{speak.language}}</label>
<select id="languageSelect">
<option value="en-us">English (United States)</option>
<option value="fr-fr">Francais (France)</option>
</select>
</div>
nls/fr-fr/setting.js
define({
"language" : "langue"
});
nls/setting.js
define({
"root" : {
"language" : "Language"
},
"fr-fr" : true // The system will accept French
});
According to this SO question and this github issue it's not possible to change locale at runtime with i18n.js.
From official docs it's not clear could we use it at runtime or not: "RequireJS will use the browser's navigator.language or navigator.userLanguage property to determine what locale values to use for my/nls/colors, so your app does not have to change. If you prefer to set the locale, you can use the module config to pass the locale to the plugin"
So after some researches and walk throw the i18n.js source code I found that the best solution for you will be to keep the same structure and use location.reload().

Async loading a module property

I've defined a module (module1) which is supposed to load the value of a property asynchronously. How can I use this property in my app as soon as it is defined and only after it is defined?
My setup (simplified)
v1
app.js
require(['module1'], function(mod) {
document.getElementById("greeting").value = mod.getPersonName();
});
module1.js
define(['jquery'], function($) {
_person;
$.get('values/person.json')
.done(function(data) {
_person = data
});
return {
getPersonName: function() { return _person.name; }
}
values/person.json
{ name: 'John Doe', age: 34 }
This only works if the GET happens nearly instantaneously, otherwise it fails because _person is undefined when getPersonName is called.
v2
To counter this, I figured I would register a callback to notify the app when person was loaded.
app.js
require(['module1'], function(mod) {
mod.onPersonLoaded(function() {
document.getElementById("greeting").value = mod.getPersonName();
});
});
module1.js
define(['jquery'], function($) {
_person;
_onLoaded;
$.get('values/person.json')
.done(function(data) {
_person = data;
_onLoaded();
});
return {
getPersonName: function() { return _person.name; },
onPersonLoaded: function(cb) { _onLoaded = cb; }
}
}
This works if the GET is slow, however, if it's quick _onLoaded is undefined when .done() is called.
Is there a good way to use _person values in app.js as soon as they are defined and only once they are defined?
I'm using RequireJS, but my question is generally applicable to AMD.
Edit
In simplifying my example, I removed a layer which may be important. I'm using RactiveJS for the UI.
Setup (slightly less simplified)
app.js
require(['Ractive', 'module1'], function(Ractive, mod) {
var ractive = new Ractive({
...
data : {
name: mod.getPersonName()
}
});
ractive.observe(...);
ractive.on(...);
});
Edit 2
My current solution, subject to change. Register a callback that notifies app.js when person is loaded. Callback is called immediately if person is already loaded when callback is registered.
app.js
require(['Ractive', 'module1'], function(Ractive, mod) {
var ractive = new Ractive({
...
data : {}
});
mod.watchPerson(function() {
ractive.set('person.name', mod.getPersonName());
});
ractive.observe(...);
ractive.on(...);
});
module1.js
define(['jquery'], function($) {
_person;
_onLoaded;
$.get('values/person.json')
.done(function(data) {
_person = data;
try {
_onLoaded();
} catch (e) {
// that was fast!
// callback will be called when it is registered
});
return {
getPersonName: function() { return _person.name; },
watchPerson: function(cb) {
_onLoaded = cb;
if(_person != null) {
_onLoaded();
}
}
}
}
Promises are a good choice here because callbacks are always called asynchronously - you never encounter that confusing situation where _onLoaded() gets called before it's designed.
Unfortunately, jQuery's promise implementation doesn't adhere to the Promises/A+ specification, so you don't get that guarantee. If possible, you could use an alternative AJAX library like Reqwest, and do something like
app.js
define(['module1'], function (mod) {
mod.then(function(person) {
// do something with person.name
}
});
module1.js
define(['reqwest'], function (reqwest) {
return reqwest('values/person.json');
});
Using the text loader plugin
Another option, since you're already using AMD, would be to use a loader plugin, such as text:
app.js
define(['module1'], function (person) {
// do something with person.name
});
module1.js
define(['text!values/person.json'], function (personJSON) {
return JSON.parse(personJSON);
});
Using the text loader plugin
In fact there's even a JSON plugin, so you could do away with module1 entirely in this example situation:
app.js
define(['json!values/person'], function (person) {
// do something with person.name
});
This is how I would do this. Basically, it is not much different from your V2, but it adds more incapsulation.
app.js
require(['module1'], function(mod) {
mod.loadPerson(function(person) {
document.getElementById("greeting").value = person.getPersonName();
});
});
module1.js
define(['jquery'], function($) {
return {
loadPerson : function(callback) {
$.get('values/person.json').done(function(data) {
_person = data;
callback({
getPersonName: function() { return _person.name; }
});
});
}
}
}
You may also use promises promises instead of simple callback.

Why is YUI.add required when specifying YUI modules and if it is required how can non-YUI modules work?

We use the YUI3 loader to manage loading our javascript and css files. As part of the bootstrap js code on each page, we have something like the following:
YUI({
...
groups: {
...
myGroup: {
modules: {
"my-module": {
...
path: "MyModule.js",
requires: [ "yui-base" ]
},
}
...
}
}
}).use("my-module", function (Y) {
Y.MyModule.doStuff();
});
MyModule.js has something like the following:
YUI.add('my-module', function (Y) {
Y.MyModule = function () {
...
_validator: Y.Lang.isString
};
}, '3.4.0', {
requires: [ "yui-base" ]
});
YUI also claims here that the loader can be used with non-YUI3 "modules" given that they have their dependencies specified in the configuration. They give the following example module configuration for a yui2 group:
yui2: {
combine: true,
base: 'http://yui.yahooapis.com/2.8.0r4/build/',
comboBase: 'http://yui.yahooapis.com/combo?',
root: '2.8.0r4/build/',
modules: { // one or more external modules that can be loaded along side of YUI
yui2_yde: {
path: "yahoo-dom-event/yahoo-dom-event.js"
},
yui2_anim: {
path: "animation/animation.js",
requires: ['yui2_yde']
}
}
}
This suggests that YUI is smart enough to load and run YUI2's animation.js only after yahoo-dom-event.js has loaded and run.
What I don't understand is, if this works for non-YUI modules, how come I have to wrap my own modules with YUI.add and the redundant requires list (since requires is also specified in the configuration)?
I tried dropping the add wrapper (I replaced it with (function (Y) { /* module content */ })(YUI);), but this lead to a js error on page load: Y.Lang is undefined. Thus, it seems that somehow without the wrapping add() call the script is getting executed before the base yui script where Y.Lang is defined. However, if that is the case, then won't this be a problem for non-YUI modules (which don't call YUI.add())?
It's important to distinguish between custom modules that utilize YUI3 features (sandboxed Y.Lang, etc) and completely external code.
In the first case, the YUI.add() wrapper is always necessary, because the sandbox Y variable isn't available outside the module callback (the second argument to YUI.add()). The repetition of module configuration is unfortunately necessary in hand-written modules due to constraints within Y.Loader (where the combo-loading magic happens). Modules that employ YUI's build tools have the wrapper and metadata added automagically.
With completely external code, you only need to provide the fullpath config property, and YUI will do the right thing. Internally, YUI knows when a given <script> request finishes, and associates that success with the configured module name.
To simplify things, I'll be using YUI.applyConfig to demonstrate the config bits. Using that, you can create any number of YUI sandboxes (via YUI().use(...)) with the config mixed in, instead of repeating it all over the place.
YUI.applyConfig({
"modules": {
"leaflet": {
"fullpath": "http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"
},
"my-leaflet-thing": {
"path": "path/to/my-leaflet-thing.js",
"requires": [
"base-build",
"node-base",
"leaflet"
]
}
}
});
my-leaflet-thing.js looks something like this:
YUI.add("my-leaflet-thing", function (Y) {
// a safe reference to the global "L" provided by leaflet.js
var L = Y.config.global.L;
Y.MyLeafletThing = Y.Base.create("myLeaflet", Y.Base, {
initializer: function () {
var id = this.get('node').get('id');
var map = L.map(id);
// etc
}
}, {
ATTRS: {
node: {
getter: Y.one
}
}
});
// third argument is a version number,
// but it doesn't affect anything right now
}, "1.0.0", {
"requires": [
"base-build",
"node-base",
"leaflet"
]
});
Given this setup, since this requires a non-asynchronous library, you can safely do this:
YUI().use("my-leaflet-thing", function (Y) {
var instance = new Y.MyLeafletThing({
"node": "#foo"
});
});
Note: If an external file does dynamic loading of its own (e.g., async Google Maps API), YUI will only be aware of the initial request success, not the entire chain of files loaded. To address this, you'll need to use the querystring callback argument in the fullpath config, associated with some globally-exposed callback in the module that requires it.
In these cases, it's better to do an internal Y.use() (note the sandbox variable) to better encapsulate the required globals.
Config:
YUI.applyConfig({
"modules": {
"google-maps-api": {
"fullpath": "http://maps.googleapis.com/maps/api/js" +
"?v=3&sensor=false&callback=initGMapsAPI"
},
"my-google-map-thing": {
"path": "path/to/my-google-map-thing.js",
"requires": [
"base-build",
"node-base"
]
}
}
});
my-google-map-thing.js:
YUI.add("my-google-map-thing", function (Y) {
// publish a custom event that will be fired from the global callback
Y.publish('gmaps:ready', {
emitFacade: true,
fireOnce: true
});
// private sentinel to determine if Y.use() has been called
var isUsed = false;
// expose global function that matches "callback" parameter value
Y.config.global.initGMapsAPI = function () {
// Y.config.global.google is now available
Y.fire('gmaps:ready');
};
Y.MyGoogleMapThing = Y.Base.create("myGoogleMap", Y.Base, {
initializer: function () {
Y.on('gmaps:ready', this.render, this);
if (!isUsed) {
isUsed = true;
Y.use("google-maps-api");
}
},
render: function () {
// safe reference to global "google"
var google = Y.config.global.google;
var id = this.get('node').get('id');
var map = new google.maps.Map(id, {
// ...
});
// etc
}
}, {
ATTRS: {
node: {
getter: Y.one
}
}
});
}, "1.0.0", {
"requires": [
"base-build",
"node-base"
]
});
To sum up: YUI.add() is only necessary when writing modules that depend on YUI3 sandboxed resources. Loading external code, as long as it is all synchronous, is as simple as employing the fullpath config property. Asynchronous external loading is a bit hairier, but still possible.

Categories