Reusing/sharing views & models in different projects with Durandal JS - javascript

I'm building multiple applications using Durandal JS. All those applications are located on the same server under the same document root and share some common code. For example they all use the same model & view for login.
How can i reuse/share the login model & view in all those applications without just copy & pasting the files to the projects?
I already tried something with the following folder structure:
ProjectsDir/Project1/app/durandal/..
/models/Shell.js, Main.js, ...
/views/Shell.html, Main.html, ...
/main.js
/main-built.js
ProjectsDir/Project2/app/durandal/..
/models/Shell.js, Main.js, ...
/views/Shell.html, Main.html, ...
/main.js
/main-built.js
ProjectsDir/ProjectsBase/app/models/Login.js
/views/Login.html
This way it would be possible to reference the same login model & view in my ProjectsBase from all other projects by setting the correct route to it in the respective shell.js. This route could look something like this:
router.map([
{
url: 'Login',
moduleId: '../../ProjectsBase/app/models/Login',
name:'Login',
visible: true
},
{
url: 'Main',
moduleId: 'models/Main',
name:'Main',
visible: true
}
]);
This works as expected during debugging but building the production version with the durandal optimizer unfortunately doesn't work.
Actually building does work (it produces the main-built.js just fine) but when i launch the site with the production file referenced i get the following error:
Uncaught Error: undefined missing durandal/../../../MPBase/durandal-app/models/Login
I'd really appreciate any ideas on how I could make the built production file work with the setup I described above.
Of course I'm also open for other ideas on how to make models & views reusable/sharable between multiple projects.
Thanks

With some help from Durandals Google Group I found a solution.
It's not possible to use the provided optimizer.exe but it's quite easy to create a custom r.js config which can handle the setup I described in the question:
First of all I ran the optimizer.exe which created a basic config file (app.build.js) that i used as a starting point.
This config file automatically included all necessary files from the project itself (e.g. Project1).
The only things that are missing in this config file are the references to my shared code (the login files from the ProjectsBase directory). Therefore I added them manually along with a new path.
Custom app.build.js (3 changes highlighted with a comment, the rest is how it was built from the optizimer.exe):
{
"name": "durandal/amd/almond-custom",
"inlineText": true,
"stubModules": [
"durandal/amd/text"
],
"paths": {
"text": "durandal/amd/text",
"projectsbase": "../../ProjectsBase/" // New path to folder with shared files
},
"baseUrl": "ProjectsDir\\Project1\\app",
"mainConfigFile": "ProjectsDir\\Project1\\app\\main.js",
"include": [
"main",
"durandal/app",
"durandal/composition",
"durandal/events",
"durandal/http",
"text!durandal/messageBox.html",
"durandal/messageBox",
"durandal/modalDialog",
"durandal/system",
"durandal/viewEngine",
"durandal/viewLocator",
"durandal/viewModel",
"durandal/viewModelBinder",
"durandal/widget",
"durandal/plugins/router",
"durandal/transitions/entrance",
"projectsbase/app/models/Login", // Include for login model
"models/Main",
"models/Shell",
"text!projectsbase/app/views/Login.html", // Include for login view
"text!views/Main.html",
"text!views/Shell.html"
],
"exclude": [],
"keepBuildDir": true,
"optimize": "uglify2",
"out": "ProjectsDir\\Project1\\app\\main-built.js",
"pragmas": {
"build": true
},
"wrap": true,
"insertRequire": [
"main"
]
}
Now I only had to update my Shell.js to use the correct routes to the Login model & view by also adding a path to requirejs and using it correctly when setting the routes:
Add path at the very beginning of Shell.js:
requirejs.config({
paths: {
'projectsbase': '../../ProjectsBase/'
}
});
Set correct routes in activate method of Shell.js:
router.map([
{ url: 'Login', moduleId: 'projectsbase/app/models/Login', name:'Login', visible: true },
{ url: 'Main', moduleId: 'models/Main', name:'Main', visible: true }
]);
Now i can build my main-built.js which bundles & minifies all relevant files by opening the node js command line, browsing to the directory where the r.js config file is and create the build (the main-built.js) with the following command:
node r.js -o app.build.js
This way everything is included correctly when I'm working with the debug files and it's also working with the build main-built.js which also includes my shared files from the ProjectsBase.

Related

Failing to resolve dependency for Ext.ux

I am using ExtJS 6.2
I have the following line in my app.json:
"requires": [
"font-awesome",
"ux"
],
I am trying to create a simple text view using LiveSearchGrid.js, so I have the following in my app_name=md_registry folder md_registry/app/view/main/ListTest.js:
Ext.define('md_registry.view.main.ListTest', {
extend: 'Ext.ux.LiveSearchGridPanel',
//xtype: 'row-expander-grid',
//store: 'Patients',
xtype: 'mainlisttest',
requires: [
'md_registry.store.Patients',
'Ext.ux.LiveSearchGridPanel'
],
When I try doing a sencha app build, I get the following compiler error:
Failed to resolve dependency Ext.ux.LiveSearchGridPanel for file md_registry.view.main.ListTest
I have verified that this file exists in the path:
md_registry/ext/packages/ux/classic/src
From everything I've read, specifying the above requires in my app.json should be sufficient, but it's obviously not.
You are right now creating a universal app, but not using the correct folders for your code. You should definitely look into either creating a classic-only app, or a universal app with correct folder structure. If you want to make a universal app, but not now, you can compile only classic.
The solution was completely unobvious:
Had to comment this out from the "builds" profile in app.json:
"modern": {
"toolkit": "modern",
"theme": "theme-triton",
"sass": {
"generated": {
"var": "modern/sass/save.scss",
"src": "modern/sass/save"
}
}
}
I hope this helps someone!

Systemjs builder remove file extension

I have couple of es6 modules. add.js, sub.js and calc.js.
calc.js imports add.js and sub.js.
I am building them with grunt and SystemJS builder. My grunt configuration for this looks like -
systemjs: {
es6: {
options: {
baseURL: "/",
configFile: "config.js",
},
files: [{
"src": ["src/main/calc.js"],
"dest": "dist/calc.js"
}]
}
}
Once the grunt build is done, it creates the resultant calc.js and in that file I can see this :
System.register("src/main/calc.js", [..
..................
I don't want the .js in that name. I want it something like :
System.register("src/main/calc", [..
What should I do? Am I missing some configuration ?
This naming behavior is by design. bundle() creates a bundle for SystemJs to use in the browser, so the names must match the file names that SystemJs will be attempting to load. If you are trying to avoid SystemJs in the browser, buildStatic() is what you are looking for.
There is paths option for the builder.
paths: {
"src/main/calc": "src/main/calc.js"
}
FYI, there is map option. I thought map would be the option for the purpose but it never worked for me.

RequireJS optimize multi-page app using map config

I'm trying to modularize my existing project by breaking out functionality into separate applications that share a lot of common code. It's a Backbone/Marionette app, and everything is working fine in development mode, but I'm having trouble getting optimization to work. I currently have two pages, with 2 main files and 2 application files. The main files both contain requirejs.config blocks which are almost identical, except the second one uses the map config option to map the app module to loginApp. The reason for this is that most of the other modules depend on the app module for some application-wide functionality, including messaging and some global state variables.
main.js:
requirejs.config({
shim: { ... },
paths: { ... }
});
define(['vendor'], function() {
// This loads app.js
require(['app'], function(Application) {
Application.start();
});
});
main-login.js:
requirejs.config({
shim: { ... },
paths: { ... },
map: {
"*": { "app": "loginApp" }
}
});
define(['vendor'], function() {
// This loads loginApp.js because of the mapping above
require(['app'], function(Application) {
Application.start();
});
});
This works great until I optimize. I'm getting an error about a missing file, but having worked with requirejs long enough, I know that really has nothing to do with the problem. :)
From the docs:
Note: when doing builds with map config, the map config needs to be
fed to the optimizer, and the build output must still contain a
requirejs config call that sets up the map config. The optimizer does
not do ID renaming during the build, because some dependency
references in a project could depend on runtime variable state. So the
optimizer does not invalidate the need for a map config after the
build.
My build.js file looks like this:
({
baseUrl: "js",
dir: "build",
mainConfigFile: "js/main.js",
removeCombined: true,
findNestedDependencies: true,
skipDirOptimize: true,
inlineText: true,
useStrict: true,
wrap: true,
keepBuildDir: false,
optimize: "uglify2",
modules: [
{
name: "vendor"
},
{
name: "main",
exclude: ["vendor"]
},
{
name: "main-login",
exclude: ["vendor"],
override: {
mainConfigFile: "js/main-login.js",
map: {
"*": {
"app": "loginApp"
}
}
}
}
]
});
I'd like to avoid having 2 separate build files, if possible, and I'm working on breaking out the requirejs.config block into a single, shared file and having the 2 main files load that and then load the app files (this is similar to how the multipage example works) but I need that map config to work in the optimizer in order for this to work. Any ideas what I'm missing here?
Update
I've split out the config into its own file, config.js, which gets included by the main-* files. In the main-login.js file, I include the map config above the define and everything works in development mode.
require.config({
map: {
"*": {
"app": "loginApp"
}
}
});
define(['module', 'config'], function(module, config) {
...
The build.js file is the same as above, except with the second mainConfigFile removed. Optimization still fails, though. What I think is happening is, since this is a Marionette app, it's common practice to pass the Application object as a dependency to other parts of the app, including views, controllers and models. When I optimize, I run into two different problems. If I leave removeCombined as true, the optimizer will build in the dependencies from the first app, then remove those files, so when it sees them in the second app, it will fail because it can't find the source files anymore. Setting this to false seems reasonable, but the problem is this then gives me the following error:
Error: RangeError: Maximum call stack size exceeded
I can't find any consistent information on this particular error. It might have something to do with the hbs plugin (similar to text but for pre-compiling Handlebars templates) but I'm not positive that's the case. Since there's no stack trace, I'm not sure where to start looking. My gut feeling is it's a circular dependency somewhere, though. So, my updated question is, how should a multi-page Marionette app be decoupled so as to make sharing code (not just 3rd party code, but custom code such as data models and views) possible? Do I need to remove any dependencies on the core Application object? (That would require an awful lot of refactoring.) Since it works just fine in development mode, is there some trick to r.js's config I'm overlooking? I've tried adding app to the exclude lists as well as stubModules but nothing seems to work. I'm thinking of just creating 2 build files and being done with it, but I'd really like to know how to solve this the "right" way.
Your build file can be like this:
({
...
include: [ './config/main.js' ],
pragmasOnSave: {
excludeBuildConfig: true
}
})
You can use pragmasOnSave to tell optimizer to exclude a section in a file in optimized result, so Requirejs config file can be like following code
requirejs.config({
//>>excludeStart('excludeBuildConfig', pragmas.excludeBuildConfig)
shim: { ... },
paths: { ... },
//>>excludeEnd('excludeBuildConfig')
map: {
"*": { "app": "loginApp" }
}
});
The final solution used was to incorporate Grunt into the build workflow. Inside Grunt, I'm dynamically creating task targets to the requirejs task. I refactored my multiple applications to all use the same folder structure, so it was easy to reuse the same build config for each. There's still the minor inconvenience of compiling the vendor file multiple times, but that's a small price to pay.
Here's the function I use to create the config inside my dev task, in case anyone's interested:
var buildRequireTargets = function(appList) {
var requireTargets = {},
buildConfig = {
baseUrl: "<%= sourceDir %>/js",
dir: "<%= buildDir %>/js",
mainConfigFile: "<%= sourceDir %>/js/config.js",
removeCombined: true,
findNestedDependencies: true,
skipDirOptimize: true,
inlineText: true,
useStrict: true,
wrap: true,
keepBuildDir: true,
optimize: "none",
pragmasOnSave: {
excludeHbs: true
}
};
_.each(appList, function (app) {
requireTargets[app] = {
options: _.extend({
map: {
"*": {
"app": app + "/app"
}
},
modules: [
{
name: "vendor"
},
{
name: app + "/main",
exclude: ["vendor"]
}
]
}, buildConfig)
};
});
grunt.config("requirejs", requireTargets);
};

RequireJS Optimizer - still attempting to load files from old dir - w/ Django

Need some help with the requireJS optimizer.
My setup is as follows:
Django
JS inside the Django static folder
Folder structure
- dist
- copy of /static/ after optimizer (dir)
- django_app01
- django_app02
- django_app ...
- static
- bower_components
- js
What I'm doing:
Running r.js on the static folder.
r.js optmizes and moves the files to the "dist" folder as expected
Change the "static_url" settings in Django to get the static files from the "dist" folder.
Load the page and get a bunch of 404's as RequireJS is still trying to get modules from /static/...
Here's an example of the url's it's trying to fetch:
localhost/static/...
Rather than
localhost/dist/...
Any ideas why I'm getting all of these 404's after I run the optimizer. I expected the r.js optimizer to start to look for all the files in /dist.
build.js:
mainConfigFile : "static/js/require/common.js",
baseUrl: "static",
dir: "dist",
removeCombined: false,
findNestedDependencies: false,
modules: [
{
name: "js/require/common"
},
...
]
common.js:
requirejs.config({
baseUrl: '/static/',
paths: {
'jquery': 'bower_components/jquery/dist/jquery.min',
...
},
'shim': {
'blah': {
'deps': [...],
'exports': 'blah'
},
...
}
})
html:
<head>
<script src="//cdn.jsdelivr.net/requirejs/2.1.14/require.min.js"></script>
<script src="{% static 'js/require/common.js' %}"></script>
</head>
<script>
require(['{% static "js/interviews.js" %}']);
</script>
Judging by what you do show in your question, the runtime configuration you use when you do not optimize your files is the same as the one you use after optimization. That is, you have only one common.js file that sets your runtime for both cases. This file sets a baseUrl of /static/, so that's where RequireJS looks for the files. The optimization process does not override this setting behind the scenes.
For a project of mine, what I did was to put the sources subject to optimization into a directory that is just for the source (static-src). Django does not know anything about this directory. Then either one of two things happen when I run make to build my project:
I make a non-optimized build which copies all the files from static-src to a directory named build/static-build. (There are other subdirectories for other purposes under build.
I make an optimized build which runs r.js. r.js puts its output in build/static-build.
In either case, everything ends up in the same location. STATICFILES_DIRS is set in my settings to grab files from this directory when collectstatic is run. My server is set to serve the /static/ (the value of STATIC_URL) files from the location where collectstatic collected the static files.
This is just an example. You could use something else than make. You could certainly use different directory names. You could perhaps have a build process that is a bit simpler. (I created it when I was very new to Django and never bothered changing it.)
Okay, figured out how to do this without using a watcher, or make, or any other folder copy script madness.
requireJS allows you to set default variables before you load your require.js lib at the top of your HTML.
Changes I made from my previous configuration are:
- No longer define baseUrl in your module definitions (requirejs.config)
- Define baseUrl as a default depending on the environment.
build.js:
mainConfigFile : "static/js/require/common.js",
baseUrl: "static",
dir: "dist",
removeCombined: false,
findNestedDependencies: false,
modules: [
{
name: "js/require/common"
},
...
]
common.js:
requirejs.config({
// baseUrl: '/static/', DO NOT DEFINE baseUrl here anymore
paths: {
'jquery': 'bower_components/jquery/dist/jquery.min',
...
},
'shim': {
'blah': {
'deps': [...],
'exports': 'blah'
},
...
}
})
html:
<head>
<!-- Define the baseUrl depending on environment -->
<script>
var require = {
baseUrl: {% if debug %}'/static/'{% else %}'/dist/'{% endif %}
}
</script>
<script src="//cdn.jsdelivr.net/requirejs/2.1.14/require.min.js"></script>
<script src="{% static 'js/require/common.js' %}"></script>
</head>
<script>
require(['{% static "js/interviews.js" %}']);
</script>

Adding a javascript variables file to Sencha Touch 2 android app

I've got an app I'm building in Sencha Touch 2 then packaging into android. What I want to include is a "defines.js" file which contains all my variables. (This app is to be altered for different people so this makes things easier to change)
I've got it to work fine on the browser by adding the script link to the index.html file, but when I package it up and run it on the android emulator it can't find the variables.
Any ideas? Ask if you need more information.
Currently the file resides under "resources/defines.js"
Edit:
I want to include a file containing variables for use in the app (titles etc)
It is called "defines.js".
I have linked it in the index.html using the following script tag:
<script type="text/javascript" src="resources/defines.js"></script>
I have also added into the to js section of the app.json:
"js": [
{
"path": "sdk/sencha-touch.js"
},
{
"path": "app.js",
"bundle": true, /* Indicates that all class dependencies are concatenated into this file when build */
"update": "delta"
},
{
"path": "resources/defines.js"
}
],
It works in a browser but not when I created a native android app.
You can try a more 'sencha' way.
First, remove the:
<script type="text/javascript" src="resources/defines.js"></script>
and also remove the entry on app.json:
{
"path": "resources/defines.js"
}
Then, in your app.js add the resources path for the loader:
Ext.Loader.setPath({
....
'Resources': 'resources'
});
Require the class:
Ext.application({
...
requires: [
'Resources.defines',
...
]
....
});
Define the class in the file resources/defines.js:
Ext.define('MyApp.resources.defines', {
alternateClassName: 'Defines',
singleton: true,
BASEURL: 'localhost/myapp',
APPVERSION: '0.1',
});
Tehn, you can access the object properties by:
Defines.BASEURL
Defines.APPVERSION
Hope it helps-
How are you creating packaging the android app?
Using Sencha native packaging or the cordova packaging over Sencha touch

Categories

Resources