text:
Creating Mobile Apps With KnockOutJS, PhoneGap & jQuery Mobile
In part 4 we built a complete working version of the Savings Goal Simulator as a rich web app where each view model, view and view mediator is nicely modularized. How could we leverage our browser-side markup and logic to create a mobile app, portable across multiple platforms such as IOS, Android, Windows Mobile? Well, the goal of this post is to present a potential approach. (Of course you can take a peek at the "So What?" section)
The idea behind PhoneGap is to provide a cross-mobile-platform framework allowing a self-contained web app built using HTML5 + CSS3 + Javascript to run inside a full screen web view, while allowing access to the device features such as for example: geolocation, accelerometer, camera, storage, etc..
So PhoneGap will serve as our foundation. For each platform you intend on supporting, you will use the corresponding SDK, code project templates and PhoneGap runtime. Let's take a look at the 3 most common platforms:
On IOS, the SDK and application consist of:
- a CordovaLib libray
- an XCode project template
- a main module launching a UIApplicationMain
- an AppDelegate class launching a ViewController
- a ViewController loading the main page (index.html) of your app into a WebView
On Android, the SDK and application consist of:
- an Apache Cordova JAR containing the Java implementation of PhoneGap for the Android platform
- an Eclipse project template
- a DroidGap parent class used for your own Java Droid activity class loading the main page (index.html) of your app.
- an assets/www folder containing web resources (.html, .js, .css, images, etc.)
On Windows Mobile Phone, the SDK and application consist of:
- a WP7CordovaClassLib library
- a XAML application with a phone application page containing a CordovaView
- a CordovaView including a browser window hosting the main page (index.html) of your app
- a www folder containing web resources (.html, .js, .css, images, etc.)
In summary, for each platform, PhoneGap is used to create a sort of "launcher" application, which starts a "controller", which in turns loads your main .html web page into a full-screen web view, and exposes underlying platform features through a Javascript API.
So with PhoneGap, we will keep the launcher part of our mobile app fairly small and trivial. Consequently the minimum skills and associated learning curve for each platform we'll want to target will also be more manageable. The majority of your app components will be based on standard HTML5 + CSS3 + Javascript.
The approach will consist of:
- Creating (or leveraging parts of) a minimal web app using HTML and Javascript
- Hosting the web app in a PhoneGap "launcher" app (based on the desired mobile platform)
- Leveraging jQuery Mobile UI widgets for a consistent look and feel
- Theme the app using CSS3 including transitions
- Expand the web app functionality while:
- Modularizing pages and views using a templating engine
- Partitioning application logic between view models and view mediators
- Using KnockoutJS for data-binding
Your starting point may differ based on your current skills and experience:
If you already have a significant development experience on a given platform (e.g. Mac OSX with XCode, or Windows with Visual Studio), then download the SDK for your platform and follow the corresponding Getting Started Guide. You will benefit from a more integrated experience and faster device emulation.
Note for IOS: an Apple Developer License will be required if you want to deploy the app to your device (otherwise you will only be able to use the emulator).
If you have limited overall experience, starting out with the Android platform and the Aptana Studio IDE (a custom version of the Eclipse IDE optimized for HTML5/CSS3 and Javascript development) might be easier as you can run it on any OS (Windows, Mac, Linux). To get started you will need to install the following tools (in order):
Note: I recommend the basic tutorial to create a basic Android app to ensure all the prerequisites are installed correctly. Then you can follow the PhoneGap Getting Started Guide for Android.
In the rest of this tutorial I will assume that your mobile development environment is configured and working, and that you have successfully followed the PhoneGap Getting Started Guide. In the next sections I will use the Android platform as an example.
High-Level Steps
Note: this tutorial builds upon the previous four tutorials and assumes you have built a rich interactive web application using view models, view mediators, and modularized views.
Before we get started, here is a bird's-eye-view of the steps we will be going through in details in later sections:
- Creating the actual app shell using PhoneGap's Getting Started Guide for the platform you want to start out with.
- Test the basic sample shell to make sure everything is working correctly
- Customize the assets/www folder structure including all needed JS libraries
- Add all needed references for CSS and Javascript files
- Define placeholder / container divs for each "logical" jQuery Mobile page
- Create separate sets of files for each logical page:
- view markup (.html) based on a templating engine
- view model code (.js)
- view mediator code (.js)
- Create a main application Javascript module responsible for
- loading all view markup / templates
- initialize all view mediators (wich in turn initialize their respective view model and data bind the view model to the view)
- trigger the overall rendering of the main page by jQuery Mobile
- Create a phone and a tablet version of the main web page (e.g. index.phone.html vs. index.tablet.html
- Customize the mobile controller to load the appropriate index page based on device type (phone vs. tablet)
- Tailor the look and feel (layout, styling, etc.) for the tablet vs. phone version
- Prepare for packaging
- Host your app on an "app store"
Building Our App Step-By-Step
Once we are finished the mobile version of the Savings Goal Simulator will look like this:
Let's download the version of PhoneGap from http://phonegap.com/download and extract the content to a PhoneGap folder (e.g. PhoneGapSDK). We'll use the content later.
Creating The Android Application Shell
On the Android platform, the application shell takes the form a Java class called an "activity". It inherits from the android.app.Activity which is the mobile app's main application "controller" in the Model-View-Controller sense.
So let's get started in Aptana Studio and follow the steps from the PhoneGap Getting Started With Android guide:
- From Aptana Studio, select File, New Project to start the project creation wizard
- Expand the Android folder and select "Android Application Project"
- Let's name the application and Eclipse project "Savings Goal Simulator"
- Customize the namespace e.g.: com.yournamespace.savingsgoalsimulator
- Select the minimum version of the Android SDK your app should require, e.g. API7: Android 2.1
- By default the project folder will be created in the default Eclipse workspace project unless you want to customize the path.
- Click Next
- Accept the default icon settings for now (you can customize them later) and click Next
- Accept the default kind of activity settings and click Next
- Accept the default activity settings and click Next – the project will then be created:
At this point we have a basic Android app that you can run in the simulator:
- Select Run, then Run,
- Select "Android Application" in the Run As dialog, then click Ok
- The emulator will boot Android
Then click and drag the lock to the right to unlock the virtual phone, your app will appear:
Let's take a brief look at what was generated:
Expand the src folder recursively until you can see and double-click on the MainActivity.java file.
package com.yournamespace.savingsgoalsimulator; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
- The MainActivity class inherits from the core Android Activity class
- The OnCreate method calls setContentView to create the main view based on the layout provided
- Under the res\layout folder, open the activity_main.xml resource
- The RelativeLayout includes a TextView for which the text is provided by the string resource named hello_world
- Under res\values, open the strings.xml resource and find the definition of hello_world
Once we upgrade our app to PhoneGap, we will no longer use an Android content view nor a layout. Instead we will use a web view loaded with a local web page.
Creating The PhoneGap Application Shell
PhoneGap provides a subclass of Activity called DroidGap which instead of creating an Android content view based on a layout, will create a web view and load the .html page of your choice, which like any page will load stylesheets and scripts, and run the Javascript code. So we'll make the necessary changes to upgrade the default app to PhoneGap:
Let's copy the Cordova Java archive (cordova-2.2.0.jar as of this writing) of the lib\android located in our PhoneGapSDK folder we extracted earlier to the libs folder of your project (the folder currently contains a file named android-support-v4.jar). The Cordova JAR will now appear in Aptana Studio under libs.
Now we need to add the Cordova JAR to the Java Build Path. In AptanaStudio, right click on the libs folder, select "Build Path", then select "Configure Build Path", select the "Libraries" tab, click on "Add JARs …", drill down to the libs folder of your project, select the Cordova JAR, and click OK.
The Cordova JAR will also appear under the "Referenced Libraries" folder of your project.
Let's go to the MainActivity Java class source file. Since we're no longer going to need the default Android Activity approach, let's replace the 2 imports related to android.app.Activity and android.view.Menu by a import for the org.apache.cordova.* namespace:
package com.yournamespace.savingsgoalsimulator; import android.os.Bundle; import org.apache.cordova.*
Let's change the parent class for MainActivity to the new DroidGap class:
public class MainActivity extends DroidGap
Now we'll change the implementation of the onCreate method. So let's replace the line starting with setContentView by the following code which load the content from a local url:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.loadUrl("file:///android_asset/www/index.html"); }
Let's remove the onCreateOptionsMenu method as it is no longer needed.
If we ran the app again, the emulator would tell us that the index.html could not be loaded which makes sense since we have not created it yet!
Note that PhoneGap is looking for the resource in a www folder under a logical androidasset folder. Although it might seem counter-intuitive Android maps the "androidasset" url folder to the physical assets folder! So right click on assets and create the www folder.
Under assets/www, create an index.html web page file with the following basic HTML source:
<!DOCTYPE HTML>   <title>Cordova</title>
Hello World

Re-run the app and the page should now display in the emulator. At this point we just exercized the basic ability for the DroidGap class to load a web page into a web view. We'll soon add the Cordova Javascript library to the page so we can expose the bi-directional API between the web page and the actual device. But first we need to add the permissions PhoneGap will need to the application. For that, double-click on the AndroidManifest.xml and switch to the XML view. Locate the line starting with the <application tag and insert the following lines above it, then save the XML file:
<supports-screens android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:resizeable="true" android:anyDensity="true" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
Technically your application may only need a subset of these permissions, but for this tutorial we'll add them all. Additionally, if you want the application to support orientation changes, add the following attribute before the end of the <activity tag !(located underneath the <application tag):
The whole opening <activity tag should now look as follows:
<activity android:name=".MainActivity" android:label="@string/title_activity_main" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" >
To allow our WebView to interact with the PhoneGap / Cordova plugins for the mobile device we need to add configuration information to our res resource folder. So copy the xml folder located in the android folder of the PhoneGapSDK folder to the res folder of our project:
Note: if you forget this step, later the mobile app will show an error message indicating that the Cordova class cannot be found since the WebView will not be able to locate the Cordova plugins.
As in our original Creating Rich Interactive Web Apps With KnockOut.js – Part 2 tutorial, let's further expand the www folder into subfolders, by creating a scripts subfolder, and below that a vendor subfolder. Now go back to the PhoneGap SDK folder and copy the Cordova Javascript library file (cordova-2.2.0.js as of this writing) to our new assets/www/scripts/vendor folder.
Let's create a separate application.js shell module under our assets/www/scripts folder. The module will contain the code to initialize our web page but for now, let's keep it minimal and let's provide 2 functions: InitializeApplication and OnLoad:
function InitializeApplication() { alert("Application Initialized!"); } function OnLoad() { InitializeApplication(); }
Let's switch to the index.html page and add our 2 Javascript files to our  tag:
 <title>Cordova</title>
And for now, let's add a call to the OnLoad function to the body onload event of the page:

Hello World

Now you should be able to re-run the application and see the alert pop-up.
function OnDeviceReady() { InitializeApplication(); }
Now let's setup the event listener in the OnLoad function:
function OnLoad() { document.addEventListener("deviceready", OnDeviceReady, false); }
This allows us to customize the InitializeApplication to use some of the device information exposed by PhoneGap:
function InitializeApplication() { alert("Welcome to PhoneGap " + device.cordova + " version " + device.version + " for " + device.platform + " on device: " + device.name ); }
Now let's run the application again and our alert with the device information should appear!
This concludes the creation of the basic PhoneGap application shell. In summary here is what our application does:
- Android launches our MainActivity class which inherits from the DroidGap
- The onCreate method of DroidGap creates a WebView
- The onCreate method of MainActivity sets the url content of the WebView to index.html
- Our index.html loads the Cordova library and our application.js
- The onload event of the page sets up an event listener for the PhoneGap deviceready event
- Once PhoneGap is fully initialized the deviceready event is triggered
- Our custom InitializeApplication function is called – at that point the PhoneGap API is fully operational
Applying a jQuery Mobile "Veneer"
Introduction to jQuery Mobile
Since our app is built using HTML and CSS we can create a complete custom look and feel for our app. This is fine if you are creating something truly unique like a game or something very graphical.
But if you are planning on creating an application focused on manipulating data I would recommend following mobile UI conventions. One easy way to ensure a more standardized look that your customers will feel familiar with is to leverage jQuery Mobile. You will benefit from:
- a set of common UI elements optimized for a mobile experience
- larger buttons and easier to use form controls
- toolbars, listviews, collapsible panels, grids
- navigation between "logical pages"
- cross-platform compatibility helping you create a uniform experience across devices
- customizable themes
Here is how jQuery Mobile makes this possible:
jQuery Mobile allows declaration of its custom UI elements by augmenting traditional HTML elements with new attributes. E.g.: a jQM button is created by enhancing the anchor tag with a data-role="button" attribute (as well as other attributes to add details about how the button should be rendered, such as with an icon positioned above the label):
Add Contact
As the DOM is loaded, jQuery Mobile injects additional needed elements and CSS classes based on the various data-* attributes to produce the desired effect.
I would not be providing a balanced view point if I did not mention the following associated challenges:
Since "logical pages" are defined using div elements enhanced with data-role="page" attribute which causes the physical .html page to become large and difficult to maintain:

We'll use external views based on templates and the jQuery View Loader plugin to address this issue.
Defining common elements such as the bottom toolbar for each "logical page" also requires the use of templates if we do not want to duplicate the markup over and over.
Since the markup is automatically enhanced as it is being loaded, dynamically changing elements becomes a bit more complicated since jQuery Mobile needs to be notified to "re-enhance" the new markup fragments. This will become even more apparent when we use a viewmodel to view data-binding library like KnockoutJS. But again there are some solutions.
Overall despite the learning curve and the need to make jQuery Mobile "play nice" with other libraries manipulating the DOM, the benefits strongly outweigh the challenges. This tutorial will provide the necessary guidance to increase your chances of success.
Adding A jQuery Mobile Shell
First, download the full ZIP version of jQueryMobile and extract it out locally.
Before we add the files to our project, let's create a css folder under assets/www. This will provide a central place for all CSS files needed for our application.
Let's create a jquery.mobile folder under assets/www/css
Now copy the .css files (only) and the images folder from your extracted jQueryMobile ZIP to assets/www/css/jquery.mobile. Your project structure should now look like this:
And then copy the .js files from your extracted jQueryMobile ZIP to assets/www/scripts/vendor
Since jQuery Mobile builds on top of the jQuery Core, download both the development and production versions of jQuery and copy the corresponding .js files to assets/www/scripts/vendor. Your project structure should now look like this:
Now let's go back to our index.html page and add the jQuery Mobile stylesheets. We'll also add a meta tag to provide device information which will be leveraged by jQuery Mobile to optimize rendering for the device.
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <link href="css/jquery.mobile/jquery.mobile-1.2.0.css" rel="stylesheet" type="text/css" /> <link href="css/jquery.mobile/jquery.mobile.structure-1.2.0.css" rel="stylesheet" type="text/css" />
Let's include the .js files for jQuery and jQuery Mobile in between the cordova JS and our application.js

And let's define a minimal jQuery Mobile "logical page"

 Savings Goal Simulator

 Welcome!

 powered by PhoneGap and jQueryMobile

Let's run the app in the simulator:
jQueryMobile fires an event named mobileinit as soon as it starts executing. Since we may want to override some of the framework defaults (more on that later), let's add a script with an event handler for mobileinit. Since we want to setup the event handler before jQuery Mobile starts to load, we'll define the script right before the inclusion of jQuery Mobile.

In the jQuery world, a ready event is triggered on the document once the page has fully loaded. But in jQuery Mobile, since you can have multiple "logical pages", jQuery Mobile will trigger a pageinit event on a given page once it has been created and initialized. So typically we'll setup an event handler for pageinit when triggered on the main / "home" logical page. This handler will perform any initialization specific to that page. So let's add the following script below our "logical page" definition:

Now run the application. What do you notice?
The "page-home is ready!" alert message appears BEFORE the PhoneGap alert message!
Why is that?
Well, jQuery Mobile starts processing the markup as soon as it is created while PhoneGap deviceready event is not triggered until the whole physical page is loaded and until the PhoneGap library is fully initialized.
IMPORTANT: The initialization code for our application will need to be split according to when various initializations are needed. For example:
- If we need to fetch data from a remote app, we should trigger the fetch from a [jQuery Mobile] pageinit event
- If we need to use data from or related to the device (e.g. local storage), we should retrieve the data from the deviceready [PhoneGap] event
So at this point, we have a minimal PhoneGap + jQuery Mobile application!
Transforming Our Savings Goal Simulator Web App Into A Mobile App
At the end of Creating Rich Interactive Web Apps With KnockoutJS – Part 4 we had a very modular browser-side rich web app. As you will see we'll be able to adapt the code to "make it fit" with the PhoneGap and jQuery Mobile framework. At a high-level we will need to:
- adapt our views to fit mobile device dimensions
- enhance our view markup with jQuery Mobile attributes (e.g.: data-role, etc.)
Installing The Web Version Of The Savings Goal Simulator
So let's first migrate the source of the web version of the Savings Goal Simulator to our mobile project:
- Download the source code for the web version of the Savings Goal Simulator from GitHub
- Extract and open the download source
- Copy the application.css from savings-goal-simulator-master\css\ to the assets\www\css folder of your project
- Copy only the subfolders of savings-goal-simulator-master\scripts\ to the assets\www\scripts folder of your project (do not copy the application.js)
- Rename the index.html in savings-goal-simulator-master to index.web.html
- Copy index.web.html from savings-goal-simulator-master to assets\www
- Rename the application.js in savings-goal-simulator-master to application.web.js
- Copy application.web.js in savings-goal-simulator-master to assets\www\scripts
- So far your project workspace should now reflect the new folders under assets\www\scripts and the new files:
- To make troubleshooting easier, let's make a couple changes so that we can run the web version using index.web.html in a browser. This way we can always compare our mobile version to the web version to help identify and resolve potential issues.
- Open index.web.html, look for a reference to application.js and rename it to application.web.js (since we renamed that Javascript file earlier)
- Open Google Chrome (or your favorite browser), and open the Developer Tools
- Drag index.web.html to the browser
- Test the app – it should work fine – you will notice that there is no "styling" since we did not copy the jQuery UI theme (because we will use jQuery Mobile instead)
Now we are ready to adapt our web app into a mobile app.
Rudimentary Migration Of The Savings Goal Simulator Source Code
The idea is to progressively enhance the basic index.html we created in the "Adding A jQuery Mobile Shell" section by incorporating elements of the web version of the app. The modest goal for this section is to be able to bring up the Savings Goal Simulator in the mobile device simulator.
In Creating Rich Interactive Web Apps With KnockoutJS – Part 2, we implemented asynchronous parallel loading of Javascript libraries using LAB.js to speed up the loading process of our web application. We will apply the same technique to our mobile app.
In index.html, let's load the LAB.js Javascript library after loading application.js and let's add a script block:

Now let's replace the script loading for jquery.mobile-1.2.0.js by a call to load the script via LAB.js:

If we try to run our app we will notice that the "page-home is ready!" alert never pops-up and that we see an "$ undefined" error in the console. This is due to the fact that our scripts are being loaded in parallel of our page execution. So the code we had pageinit event handler we had placed below our page definition needs to be synchronized with our LAB.js-based script loading:

Now the application should work correctly in the simulator.
We can proceed and add requests to load all other Javascript libraries we'll need:

If you run the app in the simulator or the browser you will see in the console that each of the module is being loaded.
Currently the body of our index.html page has a div with a data-role of "content", let's replace our "Welcome!" text in the div with the content of body tag from our index.web.html:

 Savings Goal Simulator

 powered by PhoneGap and jQueryMobile

As a brief reminder, the scripts of type text/html represent definition of jQuery Templates used to render views / panels within the app using data-bind tags. So the data-role="content" div contains 3 views to track the savings goal, scenarios, and corresponding forecast. If we run the app now the views will not appear as we have not yet ported over the view loading logic.
In the web version, the view loading is done in the InitializeApplication function of application.web.js. Let's copy the content of application.web.js at the end of application.js and rename the InitializeApplication we just copied to LoadApplicationViews.
Now we need to make the call to LoadApplicationViews before jQuery Mobile gets a chance to run. So let's insert a wait block before loading it using LAB.js:

If you look at the code for LoadApplicationViews you will notice that it using the jQuery View Loader library to identify and load all templates (scripts of type text/html).
Note: make sure you at least have version 0.3 of jQuery View Loader (to ensure compatibility with jQuery Mobile).
function LoadApplicationViews() { if (typeof(console) != 'undefined' && console) console.info("LoadApplicationViews starting ..."); // Configure the ViewLoader $(document).viewloader({ logLevel: "debug", success: function (successfulResolution) { InitializeViewMediators(); }, error: function (failedResolution) { // Loading failed somehow if (typeof(console) != 'undefined' && console) { console.error("index.html page failed to load"); } } }); if (typeof(console) != 'undefined' && console) console.info("LoadApplicationViews done ..."); }
You should now be able to run the application and see the views appear (in the simulator or browser).
As you can see, even though the app actually works, it is not really usable and we will definitely need adapt the layout and styling of the web app to fit the much smaller screen dimensions of a mobile device.
Adapting The UI For Mobile Use
Overall our goals will be to:
- optimize the layout of all panels to ensure you can see all critical elements of the app
- make input fields, buttons and controls larger so our fingers can easily work with them
- adding sliders where appropriate to allow easy change to numeric values
- using collapsible panels to maximize the use of space
So let's start with the "savings goal" panel first.
In Aptana Studio, expand the assets/www/script/views folder containing all our views, then expand index which contains partial views (with an underscore prefix) for the index.html page, and open the _savings-goal.view.html page:
I would recommend to make a copy of the file and name it _savings-goal.web.view.html and change the reference to the file in index.web.html:

This way we can keep a baseline of what the web version looks like while we customize the mobile version.
Currently, it consists simply of a section with pairs of label + input (or span) tags:

 Savings Goal Amount: <input id="savings-goal-amount" />
 Savings Max Duration (In Months): <input id="savings-max-duration" maxlength="3" />
 Savings Target Per Month:

Let's wrap the first pair of label + input with a div with a data-role="fieldcontain" so that jQuery Mobile can enhance it to make the field bigger.

 Savings Goal Amount: <input id="savings-goal-amount" />

Let's repeat the process for the remaining pairs:

 Savings Max Duration (In Months): <input id="savings-max-duration" maxlength="3" />

 Savings Target Per Month:

Let's add a header above all data-role="field-contain" blocks:

Goal

 Savings Goal Amount: <input id="savings-goal-amount" />

 Savings Max Duration (In Months): <input id="savings-max-duration" maxlength="3" />

 Savings Target Per Month:

If you run the app in the simulator you will notice that sometimes the jQuery Mobile "look" appears and sometimes it does not. This is due to timing issues between the web view and jQuery Mobile. To remedy this, we'll request a jQuery Mobile "refresh" of our page by triggering a create event on our page div. Let's add that as the final script statement to execute once LAB.js has completed the processing of jquery.mobile-1.2.0.js:

Let's run the app in the simulator again:
Now the 2 input fields are big enough for a phone but notice the type of keyboard that comes up if you bring focus to the amount field:
This is a text keyboard. What we need is to tell the device to use a numeric keyboard. This is accomplished using new HTML5 attributes: type, min, and max. So let's add them to the amount input tag:
<input id="savings-goal-amount" type="number" min="1" max="1000" maxlength="4" />
And let's test again:
Yippee, we now have a numeric keyboard, and notice that there is a Next button to navigate to the next input field!
Let's repeat the technique for the max number of months field:
<input id="savings-max-duration" type="number" min="1" max="120" maxlength="3" />
Now we can use the appropriate numeric keypad to enter values, but it is still not very convenient if we want to vary the values and really test their impact. What we need is the jQuery Mobile slider.
In AptanaStudio expand the assets/www/scripts/viewmediator folder and let's open the sgs.mediator.savings-goal.js module which includes the view mediator responsible for the UI interactions between the view and the viewmodel. Look for the function named sgs.mediator.savingsgoal.setupViewDataBindings and let's add the following statements to initialize sliders for our input fields:
// Add sliders to our input fields $('#savings-goal-amount').slider(); $("#savings-max-duration").slider();
The whole function should now look like this:
sgs.mediator.savingsgoal.setupViewDataBindings = function(renderedNodes, boundViewModel) { // Declare the HTML element-level data bindings $("#savings-goal-amount") .attr("data-bind","value: savingsGoalAmount"); $("#savings-max-duration") .attr("data-bind","value: savingsMaxDuration"); $("#savings-target-per-month") .attr("data-bind","text: savingsTargetPerMonthFormatted()"); // Ask KnockoutJS to data-bind the view model to the view var viewNode = $('#savings-goal-view')[0]; var viewModel = sgs.mediator.savingsgoal.getViewModel(); ko.applyBindings(viewModel, viewNode); // Initialize default for value models linked to masked fields var pageSettings = GetPageSettings(); viewModel.savingsGoalAmount(pageSettings.defaultSavingsGoal || 0); viewModel.savingsMaxDuration(pageSettings.defaultSavingsMaxDuration || 0); // Add sliders to our input fields $('#savings-goal-amount').slider(); $("#savings-max-duration").slider(); if (typeof(console) != 'undefined' && console) console.info("sgs.mediator.savingsgoal.setupViewDataBindings done!"); }
Now let's re-run the app, you should see the 2 sliders
And moving the sliders should automatically change the values.
Let's shorten our labels to save space:
- Shorten "Savings Goal Amount:" to just "Amount:"
- Shorten "Savings Max Duration (In Months)" to just "Period (In Months):"
- Shorten "Savings Target Per Month:" to "Per Month:"
Adapting The Stylesheets For Mobile Use
So far we have only used the default style sheets from jQuery Mobile. Let's integrate the original application.css from the web version.
In index.html, after the jQuery Mobile stylesheet, let's include application.css:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <link href="css/jquery.mobile/jquery.mobile-1.2.0.css" rel="stylesheet" type="text/css" /> <link href="css/jquery.mobile/jquery.mobile.structure-1.2.0.css" rel="stylesheet" type="text/css" /> <link href="css/application.css" rel="stylesheet" type="text/css" />
To allow the application to adapt to different form factors, we will create different CSS files and direct the browser to use the appropriate stylesheet based on the media directive.
Under assets/www/css, let's create two new CSS files:
- application.phone.css
- application.tablet.css
In index.html, after the include for application.css, let's add two new include statements, each specifying what values the media query should match.
<link href="css/application.tablet.css" type="text/css" rel="stylesheet" media="only screen and (max-device-width: 1024px)" />
This stylesheet should be used if the maximum device width is 1024px which usually means a tablet.
<link href="css/application.iphone.css" type="text/css" rel="stylesheet" media="only screen and (max-device-width: 480px)" />
This stylesheet should be used if the maximum device width is 480px which usually means a phone.
Now let's tweak our original application.css
Let's remove the styles details for the section element:
section { }
Let's remove the standard padding for the h2 element:
section header h2 { margin: 0; }
Let's remove the styles details for the #savings-goal-view element:
#savings-goal-view { }
Let's adjust the styles details for the #savings-goal-view label rule:
#savings-goal-view label { display: inline-block; text-align: right; width: 90px; }
Let's adjust the styles details for the #consumption-scenarios-view rule:
#consumption-scenarios-view { margin: 5px 0; }
Let's adjust the styles details for the #savings-forecast-view rule:
#savings-forecast-view { margin: 5px 0; }
Let's adjust a new rule to override the border and padding used in our jQuery Mobile ui-field-contain divs:
.ui-field-contain { border: none; padding: 0; }
Let's adjust a new rule to override the font used in our jQuery Mobile labels:
.ui-field-contain label { font-size: 10px; margin: 2px 2px; }
Let's adjust a new rule to override the width used in our jQuery Mobile sliders:
div.ui-slider { width: 30%; }
Let's create an override rule for the collapsible panels:
.ui-collapsible-inset .ui-collapsible-content { border-color: black; border-style: solid; border-width: 1px; }
Let's run the app in the browser and simulator to see our new styling:
At this point we could continue adapting the look of the Savings Goal panel, by adjusting the CSS styles to make it fit a little bit more tightly. The easiest way to do that is to use your trusted browser developer tool to learn what styles are being used by jQuery Mobile and to experiment.
I will leave this as an exercise for the reader.
Adapting The Consumption Scenarios View For Mobile Use
In the web version of the application, the different type of coffee consumption options consists or set of radio buttons layed out in a table. But at minimum for a phone version of the app we need to conserve the vertical space so we can see the goals, a subset of the consumption options, and the savings forecast.
So let's use collapsible panels for each type of consumption option such as "type" (of beverage), size, and frequency. This way as a user you can expand the option you want to play with, tweak the current and proposed settings, see the impact on the forecast, and repeat the process.
First, let's make a copy of the _consumption-scenarios.view.html file under assets/www/scripts/view/index and rename the copy to _consumption-scenarios.web.view.html and change the reference to the file in index.web.html:

Again we now have a baseline of the original web version.
Now open the _consumption-scenarios.view.html file in Aptana Studio. Locate the header tag, and let's wrap the "Weekly Coffee Consumption" title in an h2 tag:

Weekly Coffee Consumption

Right below the header but before the table, let's add a div of [jQuery Mobile] data-role "collapsible-set" indicating that contained elements of data-role "collapsible" can be grouped together.

Let's add a div of data-role "collapsible" for our consumption type option. The div will contain a h3 for the panel title, followed by a table element to hold the details of our option

Consumption Type

 Current Habits	 Proposed Change

Now we can move the markup related to the drink types from the original table over to the second row of our new table within the collapsible div:

Consumption Type

 Current Habits	 Proposed Change

 <input type="radio" id="current-drink-regular" name="CurrentDrinkType" value="Regular" /> Regular
 <input type="radio" id="current-drink-latte" name="CurrentDrinkType" value="Latte" /> Latte
 <input type="radio" id="current-drink-espresso" name="CurrentDrinkType" value="Espresso" /> Espresso

 <input type="radio" id="proposed-drink-regular" name="ProposedDrinkType" value="Regular" /> Regular
 <input type="radio" id="proposed-drink-latte" name="ProposedDrinkType" value="Latte" /> Latte
 <input type="radio" id="proposed-drink-espresso" name="ProposedDrinkType" value="Espresso" /> Espresso

Let's remove all the
 tags since we no longer need them since jQuery Mobile is enhancing our DOM and styling new elements.
Let's run in the browser and the simulator.
You will notice a couple things:
The collapsible element does expand and collapse when we toggle it
jQuery Mobile has rendered our radio buttons as regular buttons. Apparently jQuery Mobile require a radio button group to be codified as a fieldset with a data-role of "controlgroup".
So let's replace our div for the current-drink-type:

by a fieldset – note that we'll request a "mini" version (using data-mini="true") to minimize the vertical space taken by the resulting jQuery Mobile radio button group:
<fieldset id="current-drink-type" data-role="controlgroup" data-mini="true">  </fieldset>
Let's re-run:
Now let's repeat the process for the other 2 types of consumption options (Size and Frequency):
- Add a div of data-role "collapsible" for our consumption option. The div will contain a h3 for the panel title, followed by a table element to hold the details of our option.
- Move the markup related to the consumption option from the original table over to the second row of our new table within the collapsible div
- Change the div containing the radio buttons into a fieldset of data-role "radiobuttongroup"
For the frequency collapsible panel, the "Other:" option has an input field associated with it. So we will need to wrap the input field with a div of data-role "field-contain", and add HTML5 type, min and max attributes:

 <input type="text" id="current-custom-frequency" name="CurrentCustomFrequency" type="number" min="1" max="999" maxlength="3" />

To allow the input field to be positioned to the right of the radio button, we will need to move the markup for data-role "fieldcontain" inside the label tag of the radio button like so:
Other:
 <input type="text" id="current-custom-frequency" name="CurrentCustomFrequency" type="number" min="1" max="999" maxlength="3" />

If you run in the browser or simulator, you will notice that the input field is not positioned where we would like it. So let's add a CSS class on the div data-role "fieldcontain" named "other-input":

 <input type="text" id="current-custom-frequency" name="CurrentCustomFrequency" type="number" min="1" max="999" maxlength="3" />

And let's define the new class in application.css:
.other-input { display: inline-block; left: -20px; margin: 0 0 5px 0; top: -10px; width: 30px; } .other-input input { height: 24px; }
Let's re-run:
Now the positioning is much better!
For the number of drinks per day, let's create a separate collapsible panel like so:

Drinks Per Day

 Current Habits	 Proposed Change

  <input type="text" id="current-drinks-per-day" name="CurrentDrinksPerDay" /> 	  <input type="text" id="proposed-drinks-per-day" name="ProposedDrinksPerDay" />

Let's re-run:
Now we need to cleanup the bottom remaining table in _consumption-scenarios.view.html.
Let's give the table an id of consumption-summary-table:

Let's cleanup the obsolete table header and let's reuse it for our weekly summary.

 Weekly Summary	 Current Habits	 Proposed Change

And remove the obsolete column titles:

 Type:

 Size:

 Frequency:

Let's define styling for consumption-summary-table in application.css:
#consumption-summary-table { border-radius: 8px; background-color: white; width: 100%; } #consumption-summary-table th { border-bottom: 1px solid Grey; }
Let's re-run:
You will notice that we're pretty close to our goal of optimizing for the vertical height of a phone!
Adapting The Savings Forecast View For Mobile Use
First, let's make a copy of the _savings-forecast.view.html file under assets/www/scripts/view/index and rename the copy to _savings-forecast.web.view.html and change the reference to the file in index.web.html:

Again we now have a baseline of the original web version.
Now open the _savings-forecast.view.html file in Aptana Studio. To maximize vertical space, let's turn the current markup into a horizontal table where the labels become column headers. We'll also shorten the column headers and the unit of measure in each data cell:

 Forecast	 Variance	 Months

 /m	 /m	 m

Let's define styles for our new table in application.css:
#savings-forecast-table { width: 100%; } #savings-forecast-table tr td { text-align: center; } #savings-forecast-view input { width: 30px; }
Run the app:
Ideally we would like the forecast panel to be visible at all times. An idea is to integrate it in the footer of the page and "pin" the footer at the bottom of the screen.
So open the index.html page, and let's move the script include for the savings-forecast-view-template and the actual savings-forecast-view-container div into the footer:

To "pin" the header, let's add a data-position="fixed" and data-fullscreen="true" as attributes on the footer. And while were at it let's specify a different jQuery Mobile theme for the footer using data-theme="e".

Let's refresh:
This is much nicer!
More Visual Tweaks
In the index.html, let's customize the jQuery Mobile theme to be used on all its widgets

Let's increase the horizontal padding for all section elements:
section { padding: 0px 2px; }
Let's tweak the height of the #page-home div and its data-role="content" div:
#page-home { height: 100%; overflow: hidden; } #page-home div.ui-content { height: 88%; }
Let's move the "Per Month" elements from the bottom of the savings goal panel to the section header:

Goal

 Per Month:

And let's add the following styles to application.css:
#savings-goal-view header h2 { display: inline-block; } #target-per-month { display: inline-block; right: -114px; } #savings-goal-view #target-per-month label { min-width: 30px; } #savings-goal-view #savings-target-per-month { font-size: 0.8em; }
Let's remove the word "Consumption" from our collapsible panels.
Let's re-run:
The look is now a bit more "professional", the visual elements stand out and the vertical positioning is optimized.
UI "Feel" / Behavior Tweaks
Originally the web app depended on jQuery UI, but since we're usin jQuery Mobile instead, we will need to tweak the mediators associated with our panels.
Two view mediators use the jQuery UI highlight effect to provide a visual indication of key changes. But now that we have sliders, the effect would take too long since it would be triggered on each mouse move. So let's remove the effect.
Open sgs.mediator.savings-goal.js under assets\www\scripts\viewmediator and look for a call to the effect function:
viewModel.savingsTargetPerMonthFormatted.subscribe(function() { $("#savings-target-per-month") .effect('highlight', { color: 'LightGreen' }, 3000); // for 3 seconds });
and let's remove the overall statement altogether:
viewModel.savingsTargetPerMonthFormatted.subscribe(function() { // Placeholder for future use });
Open sgs.mediator.consumption-scenarios.js and repeat the process:
viewModel.savingsPerWeekFormatted.subscribe(function() { // Placeholder for future use });
So if you had been noticing exceptions for the effect call in the browser console, now these are a thing of the past.
Adding An Other jQuery Mobile Page
Currently our application has only one jQuery Mobile "logical page", that is, a div with a data-role="page". It would be nice if we could have another page to edit the coffee beverage list prices! jQuery Mobile allow the definition of multiple pages and provide associated navigation mechanisms. So let's see how to do that.
First, in the index.html, before the end of the body tag, let's add the shell for our new page:

 Shell for our price list!

We need a button to navigate to the new page. Let's edit _consumption-scenarios.view.html and add a button (div with data-role="button") in the header, after the h2 title:

Weekly Coffee Consumption
 Pricing

You will notice that we specified a data-rel attribute with a value of "dialog". This tells jQuery Mobile that we would like a pop-up transition effect and a dialog theme: dark background, rounded corners, etc.. See the Dialog documentation for more details.
If you run the app in the simulator, you should see the new Pricing button:
And clicking on it will cause the navigation to the new page to occur:
If you hit the back button, the page-home page will be displayed again (without any of our values to be lost – since the pages are just hidden/shown during the navigation process).
Like for other panels, let's externalize the markup for our new pricing page. So let's create a new markup folder (one for each page) under assets/www/scripts/views/named "pricing", and then create a file named index.view.html, an cut/paste our shell message inside the following template:

Edit Pricing

 Shell for our price list!

To render the externalized view we have to:
- include a text/html template script reference in the main index.html file
- create a container div data-bound to the template
- ask KnockoutJS to render it.
So remove the "Shell for our price list!" content from the coffee-pricing-dialog div and replace it with:

This allows the inclusion of our new jQuery template in the app.
And right below let's add our div container:

Note: the afterRender callback references a setupViewDataBindings function we have not yet created in the sgs.mediator.coffeepricing module. So if you run the app now, the content of the view will not be rendered.
Open up the sgs.mediator.coffeepricing.js module under assets/www/scripts/viewmediator and let's create a shell for the setupViewDataBindings function:
sgs.mediator.coffeepricing.setupViewDataBindings = function(renderedNodes, boundViewModel) { }
Then let's update the createViewMediator function to request data binding of the coffee pricing view container with the template. This will cause KnockoutJS to render the template using jQuery Template:
sgs.mediator.coffeepricing.createViewMediator = function (pageSettings) { // Create the view Pricing Editor view-specific view model var viewModel = sgs.model.coffeepricing.initializeViewModel(pageSettings); // Save the view model sgs.mediator.coffeepricing.setViewModel(viewModel); // Ask KnockoutJS to data-bind the view model to the view var viewNode = $('#coffee-pricing-view-container')[0]; var viewModel = sgs.mediator.coffeepricing.getViewModel(); ko.applyBindings(viewModel, viewNode); if (typeof(console) != 'undefined' && console) console.info("sgs.mediator.coffeepricing ready!"); }
Now we can expand the code for setupViewDataBindings to data-bind the coffee pricing view div with our KnockoutJS coffee pricing view model:
sgs.mediator.coffeepricing.setupViewDataBindings = function(renderedNodes, boundViewModel) { // Apply the bindings var viewNode = $('#coffee-pricing-view')[0]; var viewModel = sgs.mediator.coffeepricing.getViewModel(); ko.applyBindings(viewModel, viewNode); // Get jQuery Mobile to refresh/enhance the page $('#coffee-pricing-dialog').trigger('create'); }
Now if run the app and click on the pricing button we should see our new pricing shell page:
Let's add some input fields to our pricing/index.view.html view to capture the pricing data:

Edit Pricing

 Regular Coffee

 Tall: <input id="pricing-regular-tall" type="number" min="1" max="5" maxlength="4" />

 Grande: <input id="pricing-regular-grande" type="number" min="1" max="5" maxlength="4" />

 Venti: <input id="pricing-regular-venti" type="number" min="1" max="5" maxlength="4" />

You can repeat the process for the beverages like caffe latte and espresso.
If you run the app you will see an empty dialog since we have not created a value models for each of the pricing options in the pricing view model, nor added the data binding for the various fields yet.
If we look at the current definition of our coffee pricing view model, we'll see that it includes only one value model: "pricing":
sgs.model.coffeepricing.initializeViewModel = function (pageSettings) { // ... // Lazy-initialize the price list var priceList = sgs.model.coffeepricing.getPriceList(); var viewModel = { pricing: ko.observable(priceList) } // ... return viewModel; }
If we evaluate / inspect the both the view model and the pricing value model in the browser tools console we'll see:
But so the coffee pricing view model does not have a separate value model for each property of the pricing value model. We have two options: a) declare a value model for each property, or b) leverage the dynamic nature of Javascript to dynamically declare a new value model for each property of our pricing object. Let's choose the latter approach.
Let's add a new function named publishPriceList to our sgs.model.coffeepricing module.
sgs.model.coffeepricing.publishPriceList = function (viewModel) { var priceList = viewModel.pricing(); // Find all property names for the pricing object var productKeys = Object.keys(priceList); // Process all properties for ( var i = 0; i < productKeys.length; i++) { // Get product details var productKey = productKeys[i]; var productPrice = priceList[productKey]; // Create an observable name based on the property var observableName = 'pricing' + productKey.replace('-',''); // Check if we already have value model (observable) with the same name var valueModel = viewModel[observableName]; if (typeof(valueModel) == 'undefined') { // Dynamically create a value model valueModel = ko.observable(null); // Add it to the overall view model viewModel[observableName] = valueModel; } // Update the value model's actual pricing valueModel(productPrice); } }
You can test it in the browser console by evaluating:
You now see the new value models!
So let's add a call to the publishPriceList function from initializeViewModel:
sgs.model.coffeepricing.initializeViewModel = function (pageSettings) { // .. var priceList = sgs.model.coffeepricing.getPriceList(); var viewModel = { pricing: ko.observable(priceList) } // Publish the pricing data as individual value models sgs.model.coffeepricing.publishPriceList(viewModel); // ... return viewModel; }
This way the new value models will be created upon initialization of the view model.
Now we can add the corresponding data bindings in the setupViewDataBindings function of the sgs.mediator.coffee-pricing.js module:
sgs.mediator.coffeepricing.setupViewDataBindings = function(renderedNodes, boundViewModel) { // Ask KnockoutJS to data-bind the view model to the view $("#pricing-regular-tall").attr("data-bind","value: pricingRegularTall"); $("#pricing-regular-grande").attr("data-bind","value: pricingRegularGrande"); $("#pricing-regular-venti").attr("data-bind","value: pricingRegularVenti"); // Apply the bindings var viewNode = $('#coffee-pricing-view')[0]; var viewModel = sgs.mediator.coffeepricing.getViewModel(); ko.applyBindings(viewModel, viewNode); // Get jQuery Mobile to refresh/enhance the page $('#coffee-pricing-dialog').trigger('create'); }
If we re-run the app we now see the pricing details:
And if you update a price, close the dialog, then reopen it the new pricing is still there. But you might notice that the calculations are not reflecting the price update. And if you closed and terminated the mobile app and relaunched it your new price would be lost.
What we need is to add logic to save the new pricing data once we're done with our updates on the pricing dialog. First, let's add a save button to the pricing/index.view.html view:

  Save Pricing

Let's add a click event handler for our new button in the setupViewDataBindings function of the sgs.mediator.coffee-pricing.js module.
sgs.mediator.coffeepricing.setupViewDataBindings = function(renderedNodes, boundViewModel) { // ... // Get jQuery Mobile to refresh/enhance the page $('#coffee-pricing-dialog').trigger('create'); // Add a click handler of the Save Pricing button $('#save-pricing-button') .die('click') .bind('click',sgs.mediator.coffeepricing.savePriceList); }
Le's define the sgs.mediator.coffeepricing.savePriceList and make it call savePriceList in sgs.model.coffee-pricing.js module:
sgs.mediator.coffeepricing.savePriceList = function() { sgs.model.coffeepricing.savePriceList(); }
We just need to implement the new savePriceList function in the sgs.model.coffee-pricing.js module. For now let's create a shell:
sgs.model.coffeepricing.savePriceList = function () { }
The requirements for the savePriceList are to:
- Apply the current data of the individual price value models back to the master pricing value model
- Save our pricing value model data to local storage using the jStorage library
Here is the code:
sgs.model.coffeepricing.savePriceList = function () { var viewModel = sgs.mediator.coffeepricing.getViewModel(); var priceList = viewModel.pricing(); // Find all property names for the pricing object var productKeys = Object.keys(priceList); // Process all properties for ( var i = 0; i < productKeys.length; i++) { // Get product details var productKey = productKeys[i]; // Find the corresponding observable var observableName = 'pricing' + productKey.replace('-',''); var valueModel = viewModel[observableName]; // Update the value model var productPrice = parseFloat(valueModel()); priceList[productKey] = productPrice; } // Store the updated pricing in local storage $.jStorage.set("coffee-price-list", priceList); }
If you re-run the app, you now should be able to see the impact of updating the pricing of a regular tall coffee from 1.4 to 1.5. And if you restart the app you should see that your pricing data had been saved and restored.
In the browser developer toolls, you should be able to inspect the local storage and confirm that your can see your pricing data:
At this point you have a well-rounded mobile version of the Savings Goal Simulator!
Note: as an exercise for the reader, go ahead and externalize the content of the #page-home as a separate template named index.view.html in the assets/www/views/index folder. This will keep the overall index.html main page as small as possible.
Phone And Table Versions Of The App
Currently we have an index.html which is primarily designed to fit on a phone. And although it will take the full size of the display on a tablet, the application will still work fine. But what if we want to offer a more optimized design for a tablet such as for example do away with collapsible sections, or change the layout of the app?
Well, this is where the modularity of our views and application, plus the use of HTML and CSS will pay off.
Two approaches can be considered based on your design need:
- Have a separate main web page for the phone and for the tablet version and launch the appropriate page based on the device display size.
- Keep a single web page, but just custom CSS stylesheets to tweak the layout as needed
The first option provide the most flexibility, so let's look at how to implement it.
Let's look back at the code we used in the "controller" or "launcher" (implemented as Java Activity on the Android platform):
package com.yournamespace.savingsgoalsimulator; import android.os.Bundle; import org.apache.cordova.*; public class MainActivity extends DroidGap { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.loadUrl("file:///android_asset/www/index.html"); } }
At a high-level, we need to:
- Rename our index.html to index.phone.html
- Clone our index.phone.html into a new index.tablet.html
- Change the onCreate method to detect the device size and load index.phone.html or index.tablet.html
Here is an implementation for onCreate based on a check for the width of the device:
@Override public void onCreate(Bundle savedInstanceState) { protected float TABLET_WIDTH = 768; super.onCreate(savedInstanceState); // Get actual screen size so we can determine if the device is a tablet or not Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); // Is the width is at least the minimum width of a tablet? boolean isTablet = (width >= TABLET_WIDTH); Log.v("Device size detection", "width=" + width + " height=" + height + " isTablet=" + isTablet ); // Determine the name of the corresponding main page String deviceType = isTablet ? "tablet" : "phone"; String pageUrl = "file:///android_asset/www/index." + deviceType + ".html"; super.loadUrl(pageUrl); }
Now you can customize the index.tablet.html page to your liking, such as for example:
- Adding new functionality
- Adding new jQuery Mobile logical pages
- Adding new template views
- Splitting existing views into separate phone and tablet versions
Note: we had already included a media query and conditional inclusion of device-specific CSS (see the "Adapting The Stylesheets For Mobile Use" section details) in our main html page. So now you can expand the customization to suit the appropriate device.
Prepare For Packaging
Since packaging is different for each device OS platform, I would recommend consulting the corresponding platform documentation. But the following items for consideration apply to all platforms:
- Creating the application icon in several resolutions
- Creating the initial screen image which is displayed while the app is loading or being re-activated
- Applying code signing certificates
Hosting The Application Package On An "AppStore"
Of course, if your mobile application is for public consumption (for free or for sale) then the hosting guidelines of your platform apply (e.g. Apple AppStore, Google Play, Microsoft Windows Phone App Store).
But if your mobile application is for enterprise consumption, provided that you are enrolled in an enterprise program (e.g. iOS Developer Enterprise Program), you can create your own "custom app store" as a web application, require user authentication and authorization, tailor the app catalog based on user privileges. In that situation, each app has a link to either the package (for Android) or an application descriptor (for iOS – .plist file) which contains the location of your application package (e.g. Android .apk file, iOS .ipa file). Clicking on the link triggers an "Over The Air" installation of the application once you have approved the process.
This section was meant to just give you a sense of the possibilities, but you will need to refer the corresponding platform documentation for details.
5. Recap
Even though we touched on all the basic minimum aspects of a PhoneGap + jQuery Mobile app, in some ways we barely scratched the surface. For example, we did not cover the following topics:
- leveraging device capabilities such as media, geolocation, etc. – but you easily lookup the corresponding details in the PhoneGap documentation
- using PhoneGap plugins such as the mobile notifications
- dynamically altering jQuery Mobile-enhanced HTML controls – in a nutshell you would create custom bindingHandlers for KnockoutJS
- interfacing with web services (via AJAX)
These more advanced topics will be the subject of a separate tutorial.
Here is a quick recap of the steps we followed:
|To Do
For each "logical page" in the app:
For each view model you need (e.g one global plus one per logical page):
View Model
For each view mediator you need (e.g one per logical page):
View Mediator
Customize the application initialization:
Update the InitializeViewMediators function of the www/scripts/application.js for each "logical page":
So What?
The "PhoneGap + jQuery Mobile + jQuery ViewLoader + Knockout JS" stack allows you to create portable mobile apps by leveraging your existing HTML5 and Javascript skills, while minimizing your need to master all the details of each platform.
This is a big deal because you can:
- achieve a much greater portable code base (the platform-specific code base will be small)
- get started quickly
- use your favorite IDE
- style your mobile app using CSS3 (including transitions and effects)
- adopt the look of the base platform "automagically" (via jQuery Mobile rendering)
- debug a great portion of the app using standard browser tools (e.g.: Google Chrome, Firefox + Firebug, …)
- target one platform initially then expand to other ones over time
So if you have solid web skills it is time for you to apply them towards the creation of mobile apps!
References And Resources
PhoneGap Platform
- PhoneGap / Apache Cordova mobile framework : http://www.phonegap.com/
- PhoneGap Getting Started Guides : http://docs.phonegap.com/en/2.2.0/guidegetting-startedindex.md.html
- PhoneGap Documentation : http://docs.phonegap.com/en/2.2.0/index.html
Android Platform
- Android SDK : http://developer.android.com/sdk/installing/index.html
- Aptana Studio 3 : http://www.aptana.com/products/studio3
- Android Development Tools (ADT) plugin for Eclipse / Aptana Studio : http://developer.android.com/sdk/installing/installing-adt.html
Javascript Frameworks
- jQuery Mobile : http://jquerymobile.com/
- jQuery Mobile Documentation : http://jquerymobile.com/demos/
- jQuery Template : http://api.jquery.com/category/plugins/templates/
- jQuery ViewLoader : http://github.com/techarch/jquery-viewloader
- jQuery ViewLoader rendering engine : https://github.com/techarch/jquery-viewloader
- Knockout JS Model-View-ViewModel framework : http://knockoutjs.com/
- Knock Me Out – Ryan Niemeyer's blog : http://www.knockmeout.net/
- LAB.js Parallel and Asynchronous Javascript Loader : http://labjs.com/
- KnockoutJS afterRender : http://knockoutjs.com/documentation/template-binding.html#note4usingthe_option
- Another Look at Custom Bindings for KnockoutJS : http://www.knockmeout.net/2011/07/another-look-at-custom-bindings-for.html
- Example of KnockoutJS custom bindings for jQuery Mobile
- Savings Goal Simulator : http://savings-goal-simulator.heroku.com/
- Model-View-Controller pattern : http://c2.com/cgi/wiki?ModelViewController
- Controller pattern : http://c2.com/cgi/wiki?ApplicationController
Other Tutorials In The Same Series
- Creating Rich Interactive Web Apps With KnockoutJS – Part 1 : Introduction to KnockoutJS
- Creating Rich Interactive Web Apps With KnockoutJS – Part 2 : Using view models and view mediators
- Creating Rich Interactive Web Apps With KnockoutJS – Part 3 : Modularizing view models and view mediators
- Creating Rich Interactive Web Apps With KnockoutJS – Part 4 : Modularizing and extracting view components
Savings Goal Simulator
- Web Savings Goal Simulator Source Code
- Download the Mobile App:


text:
Red lips
So sexy babes, dirty bitches, hot wenches, naughty teens, shabby whores - here everyday! They will show you what is real debauchery and lust. Only on our site you will get a unique opportunity to view thousands of the best privat webcams show. Daily updates and 100% free content. If you do not want to just watch but communicate with the models, then choose one of the online videochat on our website. You can find them at the top and bottom of this page. Start right now and get a lot of fun.
P.S. If video chat is not displayed on this page, you may have an AdBlock enabled. Please disable it on our website, to access the video chat and normal video playback.

subset: commoncrawl


text:
|Mamool Ja Savagelands
|Min Level:
|Max Level:
|This Area requires Aht Urhgan
|Connected Areas:
|Aht Urhgan Whitegate
Aydeewa Subterrane
Bhaflau Thickets
|Quests for Wajaom Woodlands
|Mobs found in Wajaom Woodlands
|Items found in Wajaom Woodlands
The Wajaom Woodlands cover a vast stretch of land to the west of the Imperial capital. The name "Wajaom," meaning "abundance" in the language of Aht Urhgan, is a fitting term to describe the rainforest tract that also encompasses the Bhaflau Thickets to the north.
The areas are home to a rich variety of fauna, from birds that hover in the air with their rapid wingbeats to bees that build hives of enormous size. Once the Imperial Army's watchtowers and patrolled routes allowed children to safely enjoy berry gathering in the forest, but due to the Empire's weakened defenses, the woodland paths are now used by the Mamool Ja for raids on the Imperial capital.
The map for this area is bought for 3,000 gil from Riyadahf in Aht Urhgan Whitegate. This map image is courtesy of Vana'diel Atlas.
Screenshots from Wajaom Woodlands:


text:
THURSDAY, April 17, 2014 (HealthDay News) -- Some children gain weight after having their tonsils removed, but this weight gain is typically confined to younger, underweight children and doesn't seem to add to obesity rates, a new study finds.
Each year in the United States, about 500,000 children have their tonsils removed. In the new study, a team from Stanford University School of Medicine tracked outcomes for 815 children who underwent tonsillectomy.
Overall, the children's weight rose by an average of just over 6 percent within 18 months of their surgery and their body mass index (an estimate of body fat based on height and weight) rose an average of 8 percent.
The largest weight increases occurred in children who were smaller and younger than age 4 at the time of surgery. Children older than age 8 gained the least weight, and children who were already heavier before their surgery did not gain weight, according to the researchers.
One expert not connected to the study said the findings make sense.
"One possible interpretation of this clinical observation has been that some children with significant nighttime breathing issues -- like sleep apnea -- actually are underweight due to the increased work of breathing, or due to obstructive food aversions related to the size of the tonsils," said Dr. Michael Rothschild, clinical professor of otolaryngology and pediatrics at the Icahn School of Medicine at Mount Sinai in New York City.
"These children may move to a more appropriate weight for their age and height following the surgery with improved eating and sleeping, while children who are overweight might not have the same degree of weight gain," he said.
The study findings seem to bear that out -- even though many children gained weight after tonsil removal, there was only a small increase in the number of children who were obese: 14.5 percent before versus 16.3 percent after. This suggests that tonsil removal is not associated with higher obesity rates, the researchers concluded.
Two other experts not involved in the study said the findings may be useful for parents and physicians.
"This study provides helpful information to parents trying to weigh the risks and benefits of surgery for their child," said Dr. Aaron Bernard, clinical skills director at the Quinnipiac University School of Medicine in Hamden, Conn.
Dr. Lisa Liberatore, an ear, nose and throat specialist at Lenox Hill Hospital in New York City, agreed. She noted that the study "found that there is an increase in weight in some children after this surgery but it was in those children who were underweight and, in some cases, not thriving -- this would be a good thing in those children."
On the other hand, "in children who were obese before surgery, there was no gain in weight or no worsening of their obesity," Liberatore said. She believes that "parents and physicians should not avoid indicated reasons for removal of the tonsils and adenoids for fear of causing obesity."
The study was published online April 17 in JAMA Otolaryngology--Head & Neck Surgery.
More information
The U.S. National Library of Medicine has more about tonsil removal.
This article: Copyright © 2014 HealthDay. All rights reserved.


text:
Thorpedene Primary School Closed
URN: 115265Address: Delaware Road, Shoeburyness, Southend-on-Sea, Essex, SS3 9NP
Rating and reports
Latest inspection
All reports
About Thorpedene Primary School
- Type: Primary
- Boarding: No
- Religious character: None
- Local authority: Southend on Sea
- Age Range: 4 - 11
- Gender: Mixed
- Number of pupils: 550
LocationView location on Google maps
- Address: Delaware Road, Shoeburyness, Southend-on-Sea, Essex, SS3 9NP
- Region: East of England
Contact details and general enquiries
- Telephone: 01702582225
- Fax: 01702586323
At the same postcode
Give feedback on this school
Use our Ofsted Parent View Survey to tell us what you think of this school or your child's experiences.
For any general enquiries about Thorpedene Primary School, please contact the school directly.
Not what you're looking for?
Search and compare other primary schools near you.
To compare other schools and colleges, go to the search (external link).
Get report alerts for Thorpedene Primary School
You'll only receive an email alert when we publish a new report.
Read our Contacting or Working with Ofsted: privacy notice to see what we do with your personal information.
You can also set up email alerts for other parts of the website. Find out more about email alerts for Ofsted.


text:
Rants from the Hill: Sorry, Utah
"Rants from the Hill" are Michael Branch's monthly musings on life in the high country of Nevada's western Great Basin desert.
Rants from the Hill is now a podcast! Listen to an audio performance of this essay, here. You can also subscribe to the podcast in iTunes or through Feedburner for use in another podcast reader.
Out here in the high elevation desert of Silver Hills the country is rough and remote. Much of it is so inaccessible that the common detritus of the dominant endemic species, Hillbillicus nevadensis (var. redneckii), is nowhere to be seen. So while the rutted, dusty BLM roads in the sage-filled valley bottoms are beribboned with spent shell casings, Coors light bottles, and empty cans of chew, there's simply no easy way to litter the steep, rocky high country. There is only one unfortunate exception to this rule, and that is when trash is airlifted into these isolated mountains and canyons in the form of balloons. I've picked up so many trashed balloons over the years that I find myself wondering what in hell is so jolly about California, which is the nearby, upwind place where all this aerial trash originates. But maybe the prevalence of balloons in the otherwise litter-free high desert shouldn't surprise me, since millions of balloons are released in the U.S. each year. We release balloons at graduation celebrations, birthday parties, wedding ceremonies, even funerals. There is in fact a company called Eternal Ascent that will, for $1,500, load your ashes into a balloon and float them away. Balloon launches for a pet's ashes cost only $600, though, so if I go this route I'm going to advise my family to say that I was a Saint Bernard.
The moment a balloon is released it becomes trash, and this trash can cover some serious ground. A sixteen-inch diameter, helium-filled latex toy balloon will float for 24 to 36 hours, and if released can cover hundreds of miles while climbing to an altitude of 25,000 feet, where it freezes, explodes, and rains down to earth in the form of garbage, which some desert rat like me then has to tote home in his backpack. And while latex balloons will eventually biodegrade, the same is not true of metalized nylon balloons, which become a more or less permanent feature of the natural environment. That's the downside of these so-called foil balloons. The upside is that they're really shiny. Because they conduct electricity, metalized balloons also cause hundreds of blackouts in the U.S. each year by short-circuiting power lines, which perhaps suggests the vulnerability of the grid. If Cactus Ed Abbey were alive today he might enjoy the idea that the elaborate infrastructure of post-industrial capitalism can be brought down by a single, drifting, metalized Mickey Mouse.
By now you may be wondering what kind of dark-souled curmudgeon would go out of his way to profess loathing for the universally beloved balloon. And I confess that I'm taking this principled stand against balloons in part because I'd otherwise have to stand against something harder to fight, like corporate greed or global climate change. But there is one use of balloons that I approve of whole-heartedly, and that is to make one's lawn chair fly. Manned balloon flights date back to the early eighteenth century, but when Mark Twain defined a balloon as a "thing to take meteoric observations and commit suicide with," he anticipated the incredible adventure of western American folk hero "Lawnchair Larry."
Larry's heroic adventure notwithstanding, the fact remains that unless you want to fly in a lawn chair or take down the power grid, balloons are trash. Fun trash. Colorful trash. But trash just the same. Now, the problem with being both an environmentalist and a father is that whenever I rant about an issue I always end up caught in some act of complicity that exposes my hypocrisy. In this case the trouble started when our five-year-old daughter, Caroline, insisted that we celebrate her sister Hannah's ninth birthday with a balloon release. I was in a tough spot, since I had to choose between being an uptight, sanctimonious, balloon-reviling ecogeek, and being a really cool Dad who happened to be externalizing the true cost of his coolness by exporting some aerial trash downwind to Utah. I remained on the fence until Caroline explained that our balloons would not go to Utah but rather to the moon, where she intended to clean them up herself, just as soon as she becomes an astronaut.
I try to tell myself that because I've retrieved scores of trashed balloons from the remote desert I've earned the right to release a few, but I know that's just more of the same evasive horseshit we all tell ourselves every day. The plain fact is that I littered, and that I had a lot of fun doing it. I hope the folks in Utah will cut me some slack on this one. After all, a man can't just sit around.
In case you forgot, Rants from the Hill is now available as a podcast! Listen here, and subscribe to the podcast in iTunes or another podcast reader.
Essays in the Range blog are not written by the High Country News. The authors are solely responsible for the content.
Michael P. Branch is Professor of Literature and Environment at the University of Nevada, Reno, where he teaches American literature and environmental studies. He has published five books and many articles on environmental literature, and his creative nonfiction has appeared in Utne Reader, Orion, Ecotone, Isotope, Hawk and Handsaw, Whole Terrain, and other magazines. He lives with his wife and two young daughters at 6,000 feet in the western Great Basin desert of Nevada.
Follow Rants from the Hill and other Range blog posts via High Country News RSS feeds.
Not an RSSer? Get weekly updates on new HCN content, including the Range blog, by signing up for our e-mail newsletter.
Image of Larry Walters awaiting takeoff in his balloon-strung lawn chair on July 2, 1982, courtesy AP Photo/San Pedro News Pilot.
All other images courtesy the author.


text:
A native of Pennsylvania, Rick Santorum was a candidate for the Republican nomination for President of the United States in 2016, 2012, and became known as a voice for conservatives who didn't feel their voice was being represented. His grassroots approach to campaigning – including visiting every one of Iowa's 99 counties and his stunning victory in the Iowa caucuses – catapulted him to frontrunner status where he ultimately won 11 states and 3 million votes during the Republican primary process.
In June, 2012 Rick Santorum launched Patriot Voices, a grassroots and online community of Americans committed to promoting faith, family, freedom and opportunity. With a goal of recruiting one million members, Patriot Voices will transform the political landscape of our country.
Rick and his wife of 24 years, Karen, are the parents of seven wonderful children: Elizabeth, John, Daniel, Sarah Maria, Peter, Patrick and Isabella. And during his run for president, Rick spoke passionately about his belief that strong families help produce a strong economy. He shared his views that we must expand opportunities for all Americans by encouraging incentives for marriage, children, and free enterprise.
As an example of his commitment to improving our economy, Rick spoke often about his belief that we must renew America's manufacturing sector to ensure that our nation isn't just made of innovators but of those who can produce those innovations. Renewing our country's manufacturing spirit will strengthen working families and help our struggling communities return to a path of prosperity during these tough economic times. Rick also believes we must support America's domestic energy potential through pro-growth tax and regulatory reforms that will not only create jobs but strengthen our national security.
On the campaign trail, Rick was a voice for the voiceless. Voters across this country heard about Rick and Karen's youngest daughter Isabella, who was born with a condition called Trisomy 18, and who wasn't expected to survive her first birthday. Beating all the odds, Bella celebrated her 6th birthday in May of 2014 and is a joyful and loving little girl. Bella's miraculous life has only strengthened Rick and Karen's belief in the dignity of each of every life.
Furthermore, one of the key reasons that Rick chose to run for president in 2012 was because of the passage of Obamacare. He believes passionately that we must repeal Obamacare and replace it with a bottom up, patient -- not government -- driven system.
Prior to running for President, Rick served in the U.S. House of Representatives from 1991 to 1995, and in the U.S. Senate from 1995 to 2007 where he was known as one of the most successful government reformers in our history. Rick took on Washington's powerful special interests from the moment he arrived in our nation's Capitol in 1991. Along with John Boehner and Jim Nussle, Rick was a member of the "Gang of Seven" that exposed the Congressional Banking and Congressional Post Office scandals. It was this record of reform that prompted a Washington Post reporter to write that "Santorum was a tea party kind of guy before there was a tea party."
Rick was also an author and floor manager of the landmark Welfare Reform Act which passed in 1996 that has empowered millions of Americans to leave the welfare rolls and enter the workforce.
He wrote and championed legislation that outlawed the heinous procedure known as Partial Birth Abortion as well as the "Born Alive Infants Protection Act," the "Unborn Victims of Violence Act," and the "Combating Autism Act" because he believes each and every individual has value and the most vulnerable in our society need to be protected.
Rick fought to maintain fiscal sanity in Washington before it was in fashion, fighting for a balanced budget and a line item veto. He bravely proposed reforming entitlements, cutting spending and even developed a "spendometer" that added up the cost of Democrat amendments to spending bills. This record made him one of the most conservative senators in Pennsylvania's history.
Rick served eight years on the Senate Armed Services Committee where he led the fight before the attacks of September 11th, 2001 to transform our military from a Cold War force to meet today's threats. He was a leader on US-Israeli relations, authoring both the "Syria Accountability Act" and the "Iran Freedom and Support Act" which he successfully fought to pass in spite of initial opposition by President Bush. Rick firmly believes that we cannot allow Iran to obtain a nuclear weapon. An accomplished author, Rick Santorum penned the 2005 New York Times best seller It Takes a Family.
Exclusively Represented by the Harry Walker Agency.


text:
Stanislas Dehaene holds the chair of Experimental Cognitive Psychology at the Collège de France, and he is also the director of the INSERM-CEA Cognitive Neuroimaging Unit at NeuroSpin, France's most advanced neuroimaging research center. He is best known for his research into the brain basis of numbers, popularized in his book, "The Number Sense." In his new book, "Reading in the Brain," he describes his quest to understand an astounding feat that most of us take for granted: translating marks on a page (or a screen) into language. He answered questions recently from Mind Matters editor Gareth Cook.
COOK: How did you become interested in the neuroscience of reading?
DEHAENE: One of my long-time interests concerns how the human brain is changed by education and culture. Learning to read seems to be one of the more important changes that we impose to our children's brain. The impact that it has on us is tantalizing. It raises very fundamental issues of how the brain and culture interact.
As I started to do experimental research in this domain, using the different tools at my disposal (from behavior to patients, fMRI, event-related potentials, and even intracranial electrodes), I was struck that we always found the same areas involved in the reading process. I began to wonder how it was even possible that our brain could adapt to reading, given it obviously never evolved for that purpose. The search for an answer resulted in this book. And, in the end, reading forces us to propose a very different view of the relationship between culture and the brain.
COOK: What is this "new relationship"?
DEHAENE: A classical, although often implicit, view in social science is that the human brain, unlike that of other animals, is a learning machine which can adapt to essentially any novel cultural task, however complex. We humans would be liberated from our past instincts and free to invent entirely new cultural forms.
What I am proposing is that the human brain is a much more constrained organ than we think, and that it places strong limits on the range of possible cultural forms. Essentially, the brain did not evolve for culture, but culture evolved to be learnable by the brain. Through its cultural inventions, humanity constantly searched for specific niches in the brain, wherever there is a space of plasticity that can be exploited to "recycle" a brain area and put it to a novel use. Reading, mathematics, tool use, music, religious systems -- all might be viewed as instances of cortical recycling.
Of course, this view of culture as a constrained "lego" game isn't that novel. It is deeply related to the structuralist view of anthropology, as exemplified by Claude Levi-Strauss and Dan Sperber. What I am proposing is that the universal structures that recur across cultures are, in fact, ultimately traceable to specific brain systems.
In the case of reading, the shapes of our writing systems have evolved towards a progressive simplification while remaining compatible with the visual coding scheme that is present in all primate brains. A fascinating discovery, made by the American researcher Marc Changizi, is that all of the world's writing systems use the same set of basic shapes, and that these shapes are already a part of the visual system in all primates, because they are also useful for coding natural visual scenes. The monkey brain already contains neurons that preferentially respond to an "alphabet" of shapes including T, L, Y. We merely "recycle" these shapes (and the corresponding part of cortex) and turn them into a cultural code for language.
COOK: In the book, you describe a part of the brain as the "letterbox." Can you please explain what you mean by that?
DEHAENE: This is the name I have given to a brain region that systematically responds whenever we read words. It is in the left hemisphere, on the inferior face, and belongs to the visual region that helps us recognize our environment. This particular region specializes in written characters and words. What is fascinating is that it is at the same location in all of us – whether we read Chinese, Hebrew or English, whether we've learned with whole-language or phonics methods, a single brain region seems to take on the function of recognizing the visual word.
COOK: But reading is a relatively recent invention, so what was the "letterbox" doing before we had written language?
DEHAENE: An excellent question – we don't really know. The whole region in which this area is inserted is involved in invariant visual recognition – it helps us recognize objects, faces and scenes, regardless of the particular viewpoint, lighting, and other superficial variations.
We are starting to do brain-imaging experiments in illiterates, and we find that this region, before it responds to words, has a preference for pictures of objects and faces. We are also finding that this region is especially attuned to small features present in the contours of natural shapes, such as the "Y" shape in the branches of trees. My hypothesis is our letters emerged from a recycling of those shapes at the cultural level. The brain didn't have enough time to evolve "for" reading – so writing systems evolved "for" the brain!
COOK: How might our brains abilities, and limits, shape other human activities, like, say mathematics?
DEHAENE: I dedicated a whole book, "The Number Sense," to our native intuitions of numbers and how they shape our mathematics. Basically, we inherit from our evolution only a rudimentary sense of number. We share it with other animals, and even infants possess it already in the first few months of life. However, it is only approximate and non-symbolic – it does not allow us to precisely distinguish 13 from 14 objects. Nevertheless, it gave humanity the concept of number, and we then learned to extend it with cultural symbols such as digits and count words, thus achieving a more precise way of doing arithmetic.
We can still find traces of this evolutionarily old system whenever we approximate, sometimes quite irrationally – for instance when we let go of one thousand dollars on an apartment sale (because it seems a small percentage of the total) while bargaining hard to obtain a carpet at 40 instead of 50 dollars!
Higher mathematics must be constrained in a similar manner by our evolutionary toolkit. Complex numbers, for instance, were deemed "imaginary" and impossible to understand until a mathematician found that they could be described intuitively as a plane – an easy-to-grasp concept for the brain.
COOK: What does this research tell us about how reading should be taught? And does it tell us anything, more generally, about how best to educate?
DEHAENE: Both of my books, "The Number Sense" and "Reading in the Brain," point to the fact that young children are more competent than we think. Learning is not "the furnishing of the mind's white paper," as John Locke said. Even for an activity as novel as reading, we do not learn from scratch, but by minimally changing our existing brain circuits, capitalizing on their pre-existing structure. Thus, teachers and teaching methods should pay more attention to the existing structure of the child's mind and brain.
In the case of reading, very concretely, as I explain in the book, we now have plenty of evidence that the whole-language approach has nothing to do with how our visual system recognizes written words – our brain never relies on the overall contours of words, rather it decomposes all of its letters and graphemes in parallel, subliminally and at a high speed, thus giving us an illusion of whole-word reading. Experiments even suggest that the whole-language method may orient learning towards the wrong brain region, symmetrical to the visual word form area in the right hemisphere! We need to inform our teaching with the best brain science – and we also need to develop evidence-based education research, using classroom experiments to verify that our deductions about teaching methods actually work in practice.
Theory, experiments on brain circuitry for reading, and education research all currently point to the superiority of grapheme-phoneme teaching methods.
COOK: What is happening in the brain of a dyslexic? Are they reading differently, or just more slowly?
DEHAENE: The dyslexic brain shows disorganized circuitry in the left temporal lobe. In the majority of dyslexic children, the phonological circuitry of the left hemisphere seems subtly disorganized, and this seems to cause a failure to learn to properly interconnect visual letter recognition with speech sounds. As a result, their visual word-form area does not develop fully, or not at the normal speed, and they continue to read serially, letter by letter or chunk by chunk, at an age where parallel reading is well established in normal readers.
We should never forget, however, that there is a great heterogeneity in dyslexia – so some children probably suffer from other difficulties, for instance related to the spatial organization of the word. Some children appear to mix left and right, or to be unable to focus on the letters sequentially from left to right without error, and this might be an additional cause of dyslexia, though somewhat less frequent that the phonological problem.
COOK: And if the brain of a dyslexic is organized differently, does that suggest that it might have other abilities -- or is dyslexia purely an impairment?
DEHAENE: This isn't fully known, but I was intrigued by recent research which indicates that dyslexic children and adults can be better on tasks of symmetry detection – they have a greater ability to notice the presence of symmetrical patterns, and the evidence even suggests that this was helpful in a group of astrophysicists to detect the symmetrical spectrum of black holes!
My theory is that mirror recognition is one of the functions that we have to partially "un-learn" when we learn to read – it is a universal feature of the primate brain that is, unfortunately, inappropriate in our alphabet where letters p, q, d and b abound. By somehow managing to maintain this ability, dyslexics might be at some advantage in visual, spatial or even mathematical tasks.
More generally, we are touching here on the very interesting issue of whether cultural recycling makes us lose some abilities that were once useful in our evolution. The brain is a finite system, so although there are overwhelming benefits of education, there might also be some losses. We are currently doing experiments with Amazon Indians, in part to test what are their native abilities and whether, in some domains such as geometry and spatial navigation, they might not be better than us.
COOK: Having done all this research, to you find yourself reading differently now, or experiencing it differently?
DEHAENE: Not really – reading has become so automatic as to be inconspicuous: as an expert reader, you concentrate on the message and no longer realize the miracles that are worked out by your brain! I am always in awe, however, when I watch young children decipher their first words – the pride on their face is a living testimony to the wonders of reading.


text:
Orthodox Church Fathers: Patristic Christian Theology Classics Search Engine
This volume contains the exegetical and homiletical writings of St. Augustine on the Gospels.
The seventh volume will be devoted to his Commentary on the Gospel and First Epistle of John, and the Soliloquies. It will be finished by the 1st of next April.
The eighth and last volume is reserved for his Commentary on the Psalms, and will appear in July, 1888.
These eight volumes will form the most complete edition of St. Augustine's Works in the English language, embracing the Edinburgh and Oxford translations, and several treatises never before translated, with introductions and explanatory notes.
Arrangements have been made for the regular issue of the Works of St. Chrysostom according to the terms of the Publisher's Prospectus, which so far has been promptly carried out. The favourable reception of the preceding volumes by the public and the press, including some leading theological journals of Europe (such as The Church Quarterly Review, and Harnack's Theologische Literaturzeitung), will encourage the editor and publisher to carry on this Patristic Library with undiminished energy and zeal.
Philip Schaff.
New York, December, 1887.


text:
Marshall Gould
September 1 - September 22, 2020
Reception: Thursday, September 10, 2020, 6-8 PMInquire View Catalog
The stunning photographs of artist Marshall Gould endeavor to tell stories that capture the decisive moment of an interesting subject while also elevating its beauty and emotional state. Though he uses modern digital equipment and software to create his works, Gould painstakingly works to maintain a technique and style that are reminiscent of the "Golden Age of Photography" which stretched from the 1930's to the 1960's. Residing in rural Northern California, his work explores Americana themes that deal with the rugged beauty of the western American landscape and the self-reliant perseverance of those who live there.
Gould's main subjects in his photographs are the semi-wild horses that roam a ranch near his home. Horses were absolutely indispensable for those who moved out to populate the American west, and as such Gould sees these subjects as emblematic of humans working in harmony with nature to exist. Further, by photographing the horses in the open space where they are allowed to live a mostly free existence, Gould is also drawing attention to creatures still existing in the natural world that we as humans need to consider in light of climate change. His photographs illustrate that humans and nature do not exist independently of one another, but truly coexist.


text:
Jesse Owens Biography
The town of Oakville witnessed the birth of a future Olympic star on September 12 of 1913. James Cleveland Owens, was the youngest of ten children born in the home of Henry and Emma Owens, a couple of sharecroppers dedicated to planting corn, cotton and other crops during the summer while the young Jesse ran around the farm.
When James Cleveland Owens was nine, his family moved to Cleveland, Ohio. Jesse attended school in Alabama in a one-room house where he was taught to read and write, but in Ohio, he was given the name of Jesse at the public school after his teacher misunderstood the accent of the boy when he said his name was J.C. Later Jesse Owens Biography would describe the episode that he wrote in his spare time. Son of a sharecropper and grandson of a slave, Jesse Owens was a weak boy that often was sick with a disease that her mother called "devil's cold" but running improved his health over time.
Henry Owens was unable to find a better job and his family was very poor. In Jesse Owens Biography, we found how he decided to help by taking different jobs after school loading freight cars, delivering groceries, and working in a shoe repair shop. In junior high school, the coach Charlie Riley saw Jesse in a gym class in which students where timed in the 60-yard dash and guessing the talent of the young Jesse, Riley invited him to run for his track team.
Because of his job, Jesse describes how he was unable to participate with Riley, but he offered to train him in the mornings, amazed by Jesse's speed. From here, the name of Jesse became the high school sensation competing in the 100-yard and 220 yard dashes. Ridley continued training him to make him a great jumper getting him into the 440-yard run. Multiple colleges recruited Jesse, but he chose Ohio State University, where he was discriminated against, not awarded with any scholarships, and was forced to live in the campus with other African American athletes.
Under these stressful circumstances and having to work to pay for school Jesse Owens' Olympic career began, being the sensation of the Olympic Games in Berlin during 1936, in the middle of Nazi propaganda promoting the racial superiority of German athletes and depicting ethnic Africans as inferior contenders. This is far from the reality you learn when you read Jesse Owens Biography to learn how he demonstrated his supremacy by winning four gold medals and receiving recognition of Ohio State University after Owens death in 1980, by completing on its campus the Jesse Owens Memorial Stadium by 2001.


text:
Once towering, now crumbling, the cliff at Lucy Vincent Beach has inspired artists and photographers throughout the world. Beginning this weekend Featherstone Center for the Arts will showcase artwork featuring the Chilmark beach and its famous cliff in both its present and past formations. The theme was suggested by Linda Ziegler, and provides an opportunity to reflect on the ways the landmark has been documented in various mediums, from painting and photography to sculpture, poetry and music.
An opening reception will take place Sunday, April 21 from 4 to 6 p.m. The show will remain open daily through May 8 from noon to 4 p.m.