Deploying a Durandaljs/Phonegap app to android - javascript

I need help, I'm at my wits end here.
I have developed an app with durandaljs and I have been doing my testing on the browser, so far it works like a dream! Then I try to deploy the app to an android device and all hell breaks loose!
I followed the directions here and here to optimize my build with Weyland using Node. I get a main-built.js file inside my app folder and a build folder pretty much containing everything in app and lib folders (eek!!!). So first question, what do I copy to my phonegap android www folder; the contents of build or the contents of my initial application?
The guide says the build includes a custom version of Almond so I don't need to load requirejs in my app (yay!) and I only have to change the requirejs script tag to point to main-built (which one the one in ./build/app/ or the one in ./app??).
So I use the one in my initial app, now I have just two script references in my index.html
<script type="text/javascript" src="phonegap.js"></script>
<script src="./app/main-built.js"></script>
Back tracking a bit, in my main.js; I kept thinking how to make sure the app didn't start until after deviceready was triggered and the dom had loaded. I saw this youtube video and I took a cue from the guys code so now my main.js before optimization looks in part like this
requirejs.config({
paths: {
'text': '../lib/require/text',
'async': '../lib/require/async',
'domReady': '../lib/require/domReady',
'durandal': '../lib/durandal/js',
'plugins': '../lib/durandal/js/plugins',
'transitions': '../lib/durandal/js/transitions',
'knockout': '../lib/knockout/knockout-2.3.0',
'bootstrap': '../lib/bootstrap/js/bootstrap',
'jquery': '../lib/jquery/jquery-1.9.1',
'jpanelmenu': '../lib/jpanelMenu/jquery.jpanelmenu.min'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define(['domReady', 'durandal/system', 'durandal/app', 'durandal/viewLocator', 'scripts/dealtag'], function (domReady, system, app, viewLocator, dealtag) {
domReady(function () {
var useragent = navigator.userAgent.toLowerCase();
if (useragent.match(/android/) || useragent.match(/iphone/) || useragent.match(/ipad/) || useragent.match('ios')) {
document.addEventListener('deviceready', onDeviceReady, false);
}
else {
onDeviceReady('desktop');
}
});
function onDeviceReady(desktop) {
if (desktop !== 'desktop')
cordova.exec(null, null, 'SplashScreen', 'hide', []);
app.title = 'My App';
app.configurePlugins({
router: true,
dialog: true,
widget: true
});
app.start().then(function () {
//Replace 'viewmodels' in the moduleId with 'views' to locate the view.
//Look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
....my own initialization code
});
}
});
So back to after optimisation, I copy the contents of build + my css folder which wasn't included in the optimisation to the www folder, run phonegap local build android; then I deploy this to my android device. The first page of the app (registration page) loads ok and in logcat I can see a lot of the same stuff that I could see in the browser console including some of my own console.log check points. However, when I click on a link to go to any other page, all I see is a white screen on my device and the following in never ending errors in logcat
Does anyone have any idea what I am doing wrong coz loads of people seem to be developing for android using durandaljs. Thanks in advance for any help you can give.

I figured out what the cause of the "confusion" is. If you have been doing your routing with Sammyjs before migrating to durandaljs, you may also come into this confusion. With sammyjs, having a route in the href attribute of your view acts like a link and automatically navigates you to the corresponding route. Something like this
My Page
or with a ko binding
<a data-bind="attr: {href: '#mypage/' + id}"></a>
This makes durandaljs go berserk! In durandal create a function in you view model like this
define(['plugins/router', 'knockout'], function (router, ko) {
var myviewmodel= function () {
this.list= ko.observableArray();
}
....
myviewmodel.prototype.showItem = function (item) {
router.navigate('mypage/' + item.id);
}
return myviewmodel;
});
and then in your view you can have
<a data-bind="click: $parent.showDeals"> <!-- if within a list context -->
or
<a data-bind="click: showDeals">
I hope this saves someone the needless grief I had to go through for hours unend trying to figure out what the problem was.

Related

Loading a third-party javascript library

I developed a web radio player using Vue Cli. Now I have to add a new functionality using an external library (it's aimed to handle audio advertising) and this library must be served from the remote host. I can't just download the library and import it locally.
Setup
In my Vue App I have several components and in one of them I have an audio tag handling the radio playback. I need to detect the click on the play button, load the ad, play it and then go to the radio regular playback.
Approachs I tried
Loading the external library in the index.html file. It works but I can't interact with the player being loaded in Vue. For example, if I try to listen to the play event in the index.html file (audio.addEventListener("play", onPlay);, I just receive "audio not defined" in the web console.
Loading the external library in the mounted () section of my component:
const triton = document.createElement('script')
triton.setAttribute('src', '//sdk.listenlive.co/web/2.9/td-sdk.min.js')
document.head.appendChild(triton)
this.initPlayerSDK()
triton.onload = () => {
let player = new TDSdk(this.tdPlayerConfig)
console.log(player)
}
The problem with this approach is that after npm run serveI receive the message 'TDSdk' is not defined which makes complete sense. I'm loading the external JS file but webpack isn't interpreting its content because that it's done in runtime. I have to add the external in my vue.config.js, but this doesn't work neither:
vue.config.js
const path = require('path')
module.exports = {
publicPath: './',
/*configureWebpack: {
externals: {
tdSdk: 'tdSdk'
}
},*/
chainWebpack: config => {
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use('url-loader')
.loader('file-loader') // not url-loader but file-loader !
.tap((options) => { // not .option() but .tap(options...)
// modify the options...
options.name = 'img/[name].[ext]'
return options
}),
config.externals([
{
'tdSdk': 'TDSdk'
},
])
},
css: {
loaderOptions: {
sass: {
sassOptions: {
includePaths: [path.resolve(__dirname, './node_modules/compass-mixins/lib')]
}
}
}
},
externals: {
tdSdk: 'TDSdk'
}
}
myComponent.vue
import tdSdk from 'tdSdk'
My solution was to load the library in the public/index.html file and then wait for the DOM being loaded so I could add the event listener to the audio element already loaded:
document.addEventListener('DOMContentLoaded', function() {
var playControl = document.getElementById('playIcon');
playControl.addEventListener("click", onPlay);
}
Then, in the Vuex store I needed to access the variables defined in the javascript located in the index.html. To do that, I set the window.adState (the var I'm using) as a global var in my store file:
Vuex.Store.prototype.$adState = window.adState
Finally, in my actions/mutations I used this.$adState to check its content:
playPause ({ commit, dispatch }) {
console.log('AdState', this.$adState)
(...)
}
Answer added on behalf of OP.
The import cannot be resolved at the time when the script is evaluated because TDSdk global is not available. A script that is dynamically added to head is loaded asynchronously so there will be race condition any way.
<script> needs to be added dynamically if there's dynamic behaviour involved or like a condition or a developer doesn't have control over page layout. For static script, Vue CLI project's public index.html can be modified:
<body>
<div id="app"></div>
<script src="//sdk.listenlive.co/web/2.9/td-sdk.min.js"></script>
<!-- built files will be auto injected -->
</body>
Application bundle is evaluated after the script and the global is expected to be available there.
Externals are commonly used for packages that were swapped to ones that were externally loaded, usually from CDN. Since tdSdk is not a real package and has no prospects to be swapped for one, it doesn't serve a good purpose to map it to a global and import it. It can be just used as TDSdk global in the application.

How can I use a videojs plugin when I also use RequireJS

I'm working on a website that includes some JS code that I do not control. That code uses RequireJS to load dependencies and all.
Disclaimer: I'm a RequireJS noob. I understand the basics, but that's pretty much it...
In my website, I need to use VideoJS. VideoJS can work with, or without RequireJS but from what I understand, if RequireJS is used somewhere in the page, I cannot use VideoJS without it.
So I'm loading VideoJS with RequireJS like this:
var MyRequire = requirejs.config({
baseUrl: '/_/js',
paths: {
videojs: 'http://vjs.zencdn.net/5.3.0/video'
}
});
MyRequire(["videojs"], function(videojs) {
videojs('myPlayer', {}, function(){
console.log('...');
});
});
And it's working.
But I want to use a VideoJS plugin to manage preroll ads. (https://github.com/dirkjanm/videojs-preroll)
I tried to include the plugin script with RequireJS, the script is included but as soon as the plugin tries to access the videojs object, I get an error telling me that videojs is not defined.
My guess is that when I load VideoJS as a RequireJS module, it's not in the global scope and the plugin that I'm using is looking for VideoJS in the global scope and that's why I get that error.
Is there any way I can use VideoJS without loading it as a RequireJS module? Or how can I help the plugin find the VideoJS object?
Thanks for your help!
You should use shim from requirejs and inject videojs into global scope.
I made an example of code for your case. I tested it and it works (you can see images below):
Loading order:
"videojs"
"add-video-js-in-global-scope"
"ads" (at this moment videojs var already in window object)
"preroll"
Requirejs analysis order:
requirejs(["preroll", "ads"] - entry point
"preroll" - requires "ads"
"ads" - requires "add-video-js-in-global-scope"
"add-video-js-in-global-scope" - requires "videojs" and add videojs var in window object.
app.js
requirejs.config({
paths: {
"videojs": "./libs/video",
"ads": "./libs/videojs.ads",
"preroll": "./libs/videojs-preroll"
},
shim:{
"preroll": {
deps: ['ads']
},
"ads": {
deps: ["add-video-js-in-global-scope"],
}
}
});
define("add-video-js-in-global-scope",["videojs"], function(videojs) {
window.videojs = videojs;
});
requirejs(["preroll", "ads"], function (preroll,ads) {
// preroll and ads will be undefined because it's not amd.
videojs('idOne', {}, function () {
var player = this;
player.ads(); // initialize the ad framework
// your custom ad integration code
});
});
index.html
<html>
<head>
</head>
<body>
<script data-main="app.js" src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.22/require.js"></script>
<div id="idOne"></div>
</body>
</html>
result:
files:

Calling require(['app']) Only Once

How can I call requireJS require(['app'], function() {}); only once at the beginning for the whole application so that any subsequent require(["..."], function(...){}); don't need to be wrapped within require(['app']?
This is my set up:
1) Load require.js
<script data-main="js/app.js" src="requirejs/require.min.js"></script>
2) Have app.js shims and basUrl configured properly.
requirejs.config({
baseUrl: "scripts/js",
paths: {
"jquery": "../bower_components/jquery/dist/jquery.min",
"modernizr": "../bower_components/modernizr/modernizr",
.
.
.
},
shim: {
"jquery.migrate": ['jquery'],
.
.
.
}
});
3) Dynamically load JS on different pages:
// Home Page
require(['app'], function() {
require(["jquery", "foundation", "foundation.reveal"], function ($, foundation, reveal){
$(document).foundation();
});
});
// Catalog Page
require(['app'], function() {
require(["jquery", "lnav/LeftNavCtrl","controllers/ProductCtrl", "controllers/TabsCtrl"], function ($, NavCtrl, ProductCtrl, TabsCtrl){
$(function() {
NavCtrl.initLeftNav();
});
});
});
Unless I wrap with require(['app'], function()) each time I call require("...") to load external JS or AMD modules, the app is not initialized and I get JavaScript errors. The above code works but it's not very efficient.
Is there a way to start my requireJS app before I try loading scripts?
I tried calling at the very beginning right after I load require.min.js:
require(["app"], function (app) {
app.run();
});
but it didn't work.
There are no provisions in RequireJS to ensure that a specific module is always loaded before any other module is loaded, other than having your first module load the rest. What you are trying to do is share your first module among multiple pages so it cannot perform the work of loading what is specific to each page.
One way you can work around this is simply to load app.js with a regular script element:
<script src="requirejs/require.min.js"></script>
<script src="js/app.js"></script>
Then the next script element can start your application without requiring app.js:
<script>
require(["jquery", "foundation", "foundation.reveal"], function ($, foundation, reveal){
$(document).foundation();
});
</script>
This is actually how I've decided to launch my modules in the applications I'm working on right now. True, it is not as optimized as it could be because of the extra network round-trip, but in the case of the applications I'm working on, they are still in very heavy development, and I prefer to leave this optimization for later.
Note that generally you don't want to use script to load RequireJS modules but your app.js is not a real module as it does not call define, so this is okay.
Another option would be to use a building tool like Grunt, Gulp, Make or something else and create one app.js per page and have each page load its own app.js file. This file would contain the configuration and the first require call to load the modules specific to your page.

Knockout with require.js - not looking for knockout in my configured 'paths' section

I’m developing a multi-page app, using requirejs to manage my javascript libs / dependencies.
My idea is that i'll have a main.js that holds the config, and then an .js file for each page that needs it, for example "register.js"
My require config is in javascripts/main.js
requirejs.config({
baseUrl: '/javascripts',
waitSeconds: 200,
paths: {
'async': 'lib/require.async',
'jquery': 'lib/jquery-1.7.2.min',
'knockout': 'lib/knockout-3.0.0'
});
I’ve got a knockout view model that looks like this:
javascripts/viewModels/userDetailsViewModel.js
define(['knockout'], function(ko) {
return function() {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
});
My ‘entry point’ is javascripts/register.js
require(['./main', 'knockout', 'viewModels/userDetailsViewModel'], function(main, ko, userDetailsViewModel) {
ko.applyBindings(new userDetailsViewModel());
});
On my register.html page, i’ve got the script reference like this:
<script data-main="/javascripts/register" src="/javascripts/lib/require.js"></script>
When my page loads, I get these errors in the console:
GET http://localhost:3000/javascripts/knockout.js 404 (Not Found)
and
Uncaught Error: Script error for: knockout
I’m not sure why it’s looking for knockout.js - I’ve specified knockout in the paths section of my config, to look in lib/knockout-3.0.0
My dir structure is:
javascripts/
Most of my pages js files go here
javascripts/viewModels
Has knockout viewmodels
javascripts/lib
Contains knockout, jquery, requirejs etc...
The problem is that RequireJS will execute the call require(['./main', 'knockout', 'viewModels/userDetailsViewModel'] without a configuration. Yes, ./main is listed before knockout but there is no order guarantee between the dependencies passed in a single require call. RequireJS may load ./main first, or knockout first. And even if ./main were loaded first by this specific call, I believe it would not have any impact on how the other modules loaded by this call would load. That is, I think this require would operate on the basis of the configuration that existed at the time it was called, and that any configuration changes caused by the modules it loads would take effect only for subsequent require calls.
There are many ways to fix this. This should work:
require(['./main', function(main) {
require(['knockout', 'viewModels/userDetailsViewModel'], function(ko, userDetailsViewModel) {
ko.applyBindings(new userDetailsViewModel());
});
});
Or you might want to restructure your files and what you pass to data-main so that your requirejs.config is loaded and executed before your first require call. Here's an example of restructuring. Change your entry point to be /javascripts/main.js:
<script data-main="/javascripts/main.js" src="/javascripts/lib/require.js"></script>
Change /javascripts/main.js so that it contains:
requirejs.config({
baseUrl: '/javascripts',
waitSeconds: 200,
paths: {
'async': 'lib/require.async',
'jquery': 'lib/jquery-1.7.2.min',
'knockout': 'lib/knockout-3.0.0'
});
require(['knockout', 'viewModels/userDetailsViewModel'], function(ko, userDetailsViewModel) {
ko.applyBindings(new userDetailsViewModel());
});
And remove /javascripts/register.js. This would be one way to do it. However, it is hard for me to tell whether this would be what you want in your specific project, because I do not know the whole project. The way to restructure for your specific project really depends on what other pages might use RequireJS, what information is common to all pages, what is specific to each page, whether you use a template system to produce HTML, etc.

Simple Dojo i18n implementation

I just recently started learning dojo for personnal use and for experience. So far, I have been doing the tutorials on various dojo stuff (on their website and over the web) and I have been "struggling" with implementing a concrete infrastructure for more complex application (or good practice). I have find one interesting project (https://github.com/csnover/dojo-boilerplate) and article (http://www.sitepen.com/blog/2011/05/04/what-is-the-best-way-to-start-a-dojo-project/). With that, I think my first problem is resolved. Correct me, if I'm wrong.
I feel like the tutorial on i18n is missing concrete implementation. For example, I would like to add i18n on the dialog box from the boilerplate project.
define([ 'dojo/_base/declare', 'dijit/Dialog' ], function (declare, Dialog) {
return declare(Dialog, {
title: 'Hello World',
content: 'Loaded successfully!'
});
});
Here, My project hierarchy is:
AS you can see, I create my own nls folder for my application and store for different (lang-locale) my "strings". Now, how do I specify the locale content on title or content for my dialog code above. I have done recently i18n on ruby on rails (with the concept of MVC) and depending on my view I had to create for this specific view a file for localization (.yml). I know that RoR and Dojo are really not the same thing, but does a widget (could be compared to my view) and so each widget needs to have their own localization... I have come accross 2 tutorials, first and second. Maybe, I'm reading it all wrong.
I have something like this right now, but it doesn't work.. What am I missing?
dojo.requireLocalization("app", "dialog");
define([ 'dojo/_base/declare', 'dijit/i18n' 'dijit/Dialog' ], function (declare, Dialog) {
i18n: dojo.i18n.getLocalization("app", "dialog"),
return declare(Dialog, {
title: i18n.title,
content: i18n.content
});
});
Thank you.
EDIT:
define([ 'dojo/_base/declare', 'dojo/i18n!app/nls/labels', 'dijit/Dialog' ], function (declare, labels, Dialog) {
return declare(Dialog, {
title: labels.title,
content: labels.content
});
});
I have no error now, but my labels.title is empty...?
EDIT(1): I forgot to add the root on the default nls folder.
Here's an example of how I have built some dialogs with localization.
directory structure
myApp\
dialog\
myDialog.js
nls\
dialog.js
fr-ca\
dialog.js
myDialog.js
define("myApp/dialog/myDialog", [
"dojo", "dijit/Dialog", "dojo/i18n",
"dojo/i18n!./nls/dialog" // this is a relative path to the
// dialog.js from myDialog.js
], function(dojo, Dialog) {
var i18n = dojo.i18n.getLocalization(
"myApp.dialog", // this is the directory path to the nls folder
"dialog" // this is the file
);
return declare(Dialog, {
title: i18n.title,
content: i18n.content
});
});

Categories