We make bad decisions once in a while and this is what our development team experienced working on an Ember project to be converted to a mobile application through Cordova. “What happened?” you would ask. We made a bad decision of independently coding the version to be converted to a mobile app with that version to be used for web and be converted to a tablet app. And now we are trying to avoid releasing a smartphone-only and tablet-only versions of the application because, let us admit it, it’s not very elegant.
It shouldn’t be a big problem if one design is perfectly responsive to all platforms of course. But that is not the case. And going for that approach while reasonable will already make the project bleed as it will entail some rework. We needed a quick fix and that’s what we tried to work out one lazy afternoon.
We got two independent distributions, one for mobile and one for tablet. We could freely dump their JS and CSS files into the Cordova project’s www folder without worrying about anything. The tricky part is keeping one index.html that will load the right script depending on the device where the application will be installed.
Locking the Screen Orientation
We needed to lock the screen orientation. As what is arguably always ideal, smartphone UI should be in portrait mode while the tablet version in landscape. After some searching, we found this plugin at http://plugins.cordova.io/#/package/net.yoik.cordova.plugins.screenorientation. It’s the only one out of the three we tried that gave us what we wanted. A short tutorial can be found here.
Dynamically Loading Scripts
For seasoned Javascript programmers this should come easy. But for us who mostly do our scripting with jQuery, it required a bit of research. A typical index.html of an Ember project to be converted to a mobile app would look like this.
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Mobile App</title> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, target-densitydpi=medium-dpi"> <link rel="stylesheet" href="styles/12345.main.css"> </head> <body> <script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="cordova.js"></script> <script src="12345.components.js"></script> <script src="12345.templates.js"></script> <script src="12345.main.js"></script> <script> //additional scripts here. </script> </body> </html>
The head contains the reference to the compiled CSS and the body contains the reference to compiled components, templates, and main JS files. Obviously we cannot keep it this way. There has to be a way to replace the references on the fly or completely remove all mentioned link an script tags and create them along the way. We did the latter.
We started with a bare index.html.
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Mobile App</title> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, target-densitydpi=medium-dpi"> </head> <body> <script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="cordova.js"></script> <script></script> </body> </html>
Just before we closed the body tag, we inserted the script that shall create the nodes we need. It was written in plain Javascript. Note that the compiled scripts contain the reference to third party scripts so we cannot use jQuery. Another important note is that it should be in index.html; well at least in our case. For some reason we could not get it to work when we moved it to the Cordova project’s app script.
/* Window must be done loading before setting orientation. */ window.addEventListener("load", load); /** * Gets user agent and check if it is mobile. * This is a very simple check and must be refined. */ var ismobile = (/mobile/i.test(navigator.userAgent.toLowerCase())); /* Dynamically creates the necessary nodes. */ var style = document.createElement("link"); style.rel = "stylesheet"; style.type = "text/css"; var component = document.createElement("script"); component.type = "text/javascript"; component.async = false; //async is being set to false so that script will not immediately fire. var templates = document.createElement("script"); templates.type = "text/javascript"; templates.async = false; var main = document.createElement("script"); main.type = "text/javascript"; main.async = false; /* Assigns stylesheet and scripts according to device. */ if(ismobile) { style.href = "styles/phone.main.css"; component.src = "scripts/phone.components.js"; templates.src = "scripts/phone.templates.js"; main.src = "scripts/phone.main.js"; } else { style.href = "styles/tablet.main.css"; component.src = "scripts/tablet.components.js"; templates.src = "scripts/tablet.templates.js"; main.src = "scripts/tablet.main.js"; } /* Finally appends the elements to head and body. */ document.getElementsByTagName("head")[0].appendChild(style); document.getElementsByTagName("body")[0].appendChild(component); document.getElementsByTagName("body")[0].appendChild(templates); document.getElementsByTagName("body")[0].appendChild(main); function load() { var ismobile = (/mobile/i.test(navigator.userAgent.toLowerCase())); if(ismobile) { screen.lockOrientation("portrait"); } else { screen.lockOrientation("landscape"); } }
We only worked on this for a short time and this can most probably be improved. Feel free to insert your suggestions. -aB
This actually answered my drawback, thank you!