Menu Module
1
2
3
4
lib/
├── menu/
│ └── menu.module.js
│ └── menu.html
The Menu module is very simple. Its purpose is to add a menu inside <ion-side-menu>. Without this module, the side menu would be blank. The menu module declares only a config file, it has ionic and ui.router as dependencies.
01
02
03
04
05
06
07
08
09
10
import modConfig from './menu.config';
let mod = angular.module('prototype.menu', [
'ionic',
'ui.router'
]);
mod.config(modConfig);
export default mod = mod.name;
The most interesting part is the configuration. We do not want to create a state for the Menu module as it is available everywhere. Instead, we decorate the root state with the menu content. With the ui-view="menu" being defined in the root state, we need to use menu@root to refer to it.
01
02
03
04
05
06
07
08
09
10
11
12
export default function($stateProvider) {
'ngInject';
$stateProvider.decorator('views', (state, parent) => {
let views = parent(state);
if (state.name === 'root') {
views['menu@root'] = {
template: require("./menu.html")
};
}
return views;
});
}
1
2
3
4
| lib/├── menu/│ └── menu.module.js│ └── menu.html |
<ion-side-menu>. Without this module, the side menu would be blank. The menu module declares only a config file, it has ionic and ui.router as dependencies.
01
02
03
04
05
06
07
08
09
10
| import modConfig from './menu.config';let mod = angular.module('prototype.menu', [ 'ionic', 'ui.router']);mod.config(modConfig);export default mod = mod.name; |
root state with the menu content. With the ui-view="menu" being defined in the root state, we need to use menu@root to refer to it.
01
02
03
04
05
06
07
08
09
10
11
12
| export default function($stateProvider) { 'ngInject'; $stateProvider.decorator('views', (state, parent) => { let views = parent(state); if (state.name === 'root') { views['menu@root'] = { template: require("./menu.html") }; } return views; });} |
Home Module
1
2
3
4
5
6
lib/
├── home/
│ └── home.module.js
│ └── home.config.js
│ └── home.controller.js
│ └── home.html
1
2
3
4
5
6
| lib/├── home/│ └── home.module.js│ └── home.config.js│ └── home.controller.js│ └── home.html |
home.module.js
The Home module displays the latests posts of your WordPress website. It has a config file, a controller, and it depends on the following libraries:
ionic
ui.router
wp-api-angularjs
01
02
03
04
05
06
07
08
09
10
11
12
13
import modConfig from './home.config';
import modController from './home.controller';
let mod = angular.module('prototype.home', [
'ionic',
'ui.router',
'wp-api-angularjs'
]);
mod.config(modConfig);
mod.controller('HomeController', modController);
export default mod = mod.name
ionicui.routerwp-api-angularjs
01
02
03
04
05
06
07
08
09
10
11
12
13
| import modConfig from './home.config';import modController from './home.controller';let mod = angular.module('prototype.home', [ 'ionic', 'ui.router', 'wp-api-angularjs']);mod.config(modConfig);mod.controller('HomeController', modController);export default mod = mod.name |
home.config.js
The config adds a new state, root.home, with the /home URL that has a template and a controller (both living within the module).
01
02
03
04
05
06
07
08
09
10
11
12
export default function($stateProvider) {
'ngInject';
$stateProvider.state('root.home', {
url: "/home",
views: {
'content@root': {
template: require("./home.html"),
controller: "HomeController as homeCtrl"
}
}
});
}
root.home, with the /home URL that has a template and a controller (both living within the module).
01
02
03
04
05
06
07
08
09
10
11
12
| export default function($stateProvider) { 'ngInject'; $stateProvider.state('root.home', { url: "/home", views: { 'content@root': { template: require("./home.html"), controller: "HomeController as homeCtrl" } } });} |
home.controller.js
This is a simplified version of the Home controller logic. It contains two functions:
loadMore: This function populates vm.posts. It uses the $wpApiPostsservice, which is part of the wp-api-angularjs library.
refresh: This function removes posts and calls loadMore again.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
export default function($scope, $log, $q, $wpApiPosts) {
'ngInject';
var vm = this;
vm.posts = [];
vm.loadMore = loadMore;
vm.refresh = refresh;
function refresh() {
vm.posts = null;
loadMore().finally(() => $scope.$broadcast('scroll.refreshComplete'));
}
function loadMore() {
return $wpApiPosts.$getList().then((response) => {
vm.posts = (vm.posts) ? vm.posts.concat(response.data) : response.data;
$scope.$broadcast('scroll.infiniteScrollComplete');
});
}
}
loadMore: This function populates vm.posts. It uses the $wpApiPostsservice, which is part of the wp-api-angularjs library.refresh: This function removes posts and calls loadMore again.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
| export default function($scope, $log, $q, $wpApiPosts) { 'ngInject'; var vm = this; vm.posts = []; vm.loadMore = loadMore; vm.refresh = refresh; function refresh() { vm.posts = null; loadMore().finally(() => $scope.$broadcast('scroll.refreshComplete')); } function loadMore() { return $wpApiPosts.$getList().then((response) => { vm.posts = (vm.posts) ? vm.posts.concat(response.data) : response.data; $scope.$broadcast('scroll.infiniteScrollComplete'); }); }} |
home.html
The template has a ion-refresher directive that allows users to reload the page by pulling the page down. It also has a ion-infinite-scroll directive that calls the loadMore function when reached. Posts are displayed using the ng-repeat directive.
Tip: Use the track by expression for better performance. It minimizes DOM manipulation when a post is updated.
01
02
03
04
05
06
07
08
09
10
<ion-view>
<ion-nav-title>Home</ion-nav-title>
<ion-content>
<ion-refresher pulling-text="Pull to refresh" on-refresh="homeCtrl.refresh()"></ion-refresher>
<div class="list card" ng-repeat="post in homeCtrl.posts track by post.ID">
<!-- THE POST DETAILS -->
</div>
<ion-infinite-scroll immediate-check="true" on-infinite="homeCtrl.loadMore()"></ion-infinite-scroll>
</ion-content>
</ion-view>
ion-refresher directive that allows users to reload the page by pulling the page down. It also has a ion-infinite-scroll directive that calls the loadMore function when reached. Posts are displayed using the ng-repeat directive.track by expression for better performance. It minimizes DOM manipulation when a post is updated.
01
02
03
04
05
06
07
08
09
10
| <ion-view> <ion-nav-title>Home</ion-nav-title> <ion-content> <ion-refresher pulling-text="Pull to refresh" on-refresh="homeCtrl.refresh()"></ion-refresher> <div class="list card" ng-repeat="post in homeCtrl.posts track by post.ID"> <!-- THE POST DETAILS --> </div> <ion-infinite-scroll immediate-check="true" on-infinite="homeCtrl.loadMore()"></ion-infinite-scroll> </ion-content></ion-view> |
Post Module
1
2
3
4
5
6
lib/
├── post/
│ └── post.module.js
│ └── post.config.js
│ └── post.controller.js
│ └── post.html
The Post module displays only one post. It has a config file, a controller, and it depends on the same librairies as the Home module.
1
2
3
4
5
6
| lib/├── post/│ └── post.module.js│ └── post.config.js│ └── post.controller.js│ └── post.html |
post.module.js
01
02
03
04
05
06
07
08
09
10
11
12
13
import modConfig from './post.config';
import modController from './post.controller';
let mod = angular.module('prototype.post', [
'ionic',
'ui.router',
'wp-api-angularjs'
]);
mod.config(modConfig);
mod.controller('PostController', modController);
export default mod = mod.name
Similar to the Home module, the config adds a new state, root.post, with the /post/:id URL. It also registers a view and a controller.
01
02
03
04
05
06
07
08
09
10
11
12
13
| import modConfig from './post.config';import modController from './post.controller';let mod = angular.module('prototype.post', [ 'ionic', 'ui.router', 'wp-api-angularjs']);mod.config(modConfig);mod.controller('PostController', modController);export default mod = mod.name |
root.post, with the /post/:id URL. It also registers a view and a controller.post.config.js
01
02
03
04
05
06
07
08
09
10
11
12
export default function($stateProvider) {
'ngInject';
$stateProvider.state('root.post', {
url: "/post/:id",
views: {
'content@root': {
template: require("./post.html"),
controller: "PostController as postCtrl"
}
}
});
}
01
02
03
04
05
06
07
08
09
10
11
12
| export default function($stateProvider) { 'ngInject'; $stateProvider.state('root.post', { url: "/post/:id", views: { 'content@root': { template: require("./post.html"), controller: "PostController as postCtrl" } } });} |
post.controller.js
The controller retrieves the post specified in the url /post/:id via the $stateParams service (UI router service).
01
02
03
04
05
06
07
08
09
10
11
12
export default function ($scope, $log, $wpApiPosts, $stateParams) {
'ngInject';
var vm = this;
vm.post = null;
$scope.$on('$ionicView.loaded', init);
function init() {
$wpApiPosts.$get($stateParams.id).then((response) => {
vm.post = response.data;
});
}
}
/post/:id via the $stateParams service (UI router service).
01
02
03
04
05
06
07
08
09
10
11
12
| export default function ($scope, $log, $wpApiPosts, $stateParams) { 'ngInject'; var vm = this; vm.post = null; $scope.$on('$ionicView.loaded', init); function init() { $wpApiPosts.$get($stateParams.id).then((response) => { vm.post = response.data; }); }} |
post.html
The template has a ion-spinner directive that displays a loader while the data is being fetched from the WordPress REST API. When the post is loaded, we use an Ionic card to render the author avatar, the post title, and the post content.
Tip: Use the bindOnce expression, ::, (introduced in Angular 1.3) to avoid watching data that will not change over time.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<ion-view>
<ion-nav-title>{{postCtrl.post.title}}</ion-nav-title>
<ion-content>
<ion-spinner ng-if="!postCtrl.post"></ion-spinner>
<div class="list card" ng-if="postCtrl.post">
<div class="item item-avatar">
<img ng-src="{{::postCtrl.post.author.avatar}}">
<h2>{{::postCtrl.post.author.name}}</h2>
<p>{{::postCtrl.post.date | date:'medium'}}</p>
</div>
<div class="item item-body">
<img class="full-image" ng-src="{{::postCtrl.post.featured_image.attachment_meta.sizes.medium.url}}">
<h2>{{::postCtrl.post.title}}</h2>
<p ng-bind-html="::postCtrl.post.content"></p>
</div>
</div>
</ion-content>
</ion-view>
ion-spinner directive that displays a loader while the data is being fetched from the WordPress REST API. When the post is loaded, we use an Ionic card to render the author avatar, the post title, and the post content.bindOnce expression, ::, (introduced in Angular 1.3) to avoid watching data that will not change over time.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
| <ion-view> <ion-nav-title>{{postCtrl.post.title}}</ion-nav-title> <ion-content> <ion-spinner ng-if="!postCtrl.post"></ion-spinner> <div class="list card" ng-if="postCtrl.post"> <div class="item item-avatar"> <img ng-src="{{::postCtrl.post.author.avatar}}"> <h2>{{::postCtrl.post.author.name}}</h2> <p>{{::postCtrl.post.date | date:'medium'}}</p> </div> <div class="item item-body"> <img class="full-image" ng-src="{{::postCtrl.post.featured_image.attachment_meta.sizes.medium.url}}"> <h2>{{::postCtrl.post.title}}</h2> <p ng-bind-html="::postCtrl.post.content"></p> </div> </div> </ion-content></ion-view> |
Style (Sass)
1
2
3
4
lib/
├── scss/
│ └── _variables.scss
│ └── bootstrap.scss
The bootstrap.scss file that we imported in our entry point is as simple as this:
1
2
@import "./variables";
@import "~ionic-sdk/scss/ionic";
First, we import our variables. We then import the Ionic styles. Importing our variables before Ionic allows us to overwrite whatever Sass variables Ionic has declared.
For example, if you want the positive color to be red instead of blue, you can overwrite it like this:
1
$positive: red !default;
1
2
3
4
| lib/├── scss/│ └── _variables.scss│ └── bootstrap.scss |
bootstrap.scss file that we imported in our entry point is as simple as this:
1
2
| @import "./variables";@import "~ionic-sdk/scss/ionic"; |
1
| $positive: red !default; |
6. Android and iOS
Installation
Run the following commands inside the project folder and chose the platform you want to build for.
1
2
3
4
$ cp config.dist.xml config.xml
$ npm run installCordova
Which platforms do you want to build? (android ios):
In addition to installing platforms within the /platforms folder, the script will install one plugin. For the demo, we need the cordova-plugin-whitelist plugin. It is necessary to allow the application to query the WordPress REST API we created earlier.
If you open config.xml, you will see that we allow access to any kind of origin (<access origin="*" />). Of course, this is only for demo purposes. If you deploy your application to production, then make sure you restrict access like this:
1
1
2
3
4
| $ cp config.dist.xml config.xml$ npm run installCordovaWhich platforms do you want to build? (android ios): |
/platforms folder, the script will install one plugin. For the demo, we need the cordova-plugin-whitelist plugin. It is necessary to allow the application to query the WordPress REST API we created earlier.<access origin="*" />). Of course, this is only for demo purposes. If you deploy your application to production, then make sure you restrict access like this:
1
|
Android
Prerequisites
- Android SDK
- Ant
Running the npm run runAndroid command is a shortcut for rm -rf www/* && webpack && cordova run android. This removes everything within the www folder, dumps a non-minified version of the app in it, and runs the android command. If an Android device is connected (run adb devices to make sure), the command will load the app on the device, otherwise it will use the Android emulator.
1
2
# Run Android
$ npm run runAndroid
npm run runAndroid command is a shortcut for rm -rf www/* && webpack && cordova run android. This removes everything within the www folder, dumps a non-minified version of the app in it, and runs the android command. If an Android device is connected (run adb devices to make sure), the command will load the app on the device, otherwise it will use the Android emulator.
1
2
| # Run Android$ npm run runAndroid |
iOS
Prerequisites
- OS X
- Xcode
If you do not have an Apple device, you should install the iOS Simulator. It's really good and better than the Android emulator.
1
$ sudo npm install -g ios-sim
Running npm run runIosEmulator is a shortcut for rm -rf www/* && webpack && cordova run ios. The npm run runIosDevice command is a shortcut for rm -rf www/* && webpack && cordova run ios --device.
1
2
3
# Run iOS
$ npm run runIosEmulator
$ npm run runIosDevice
1
| $ sudo npm install -g ios-sim |
npm run runIosEmulator is a shortcut for rm -rf www/* && webpack && cordova run ios. The npm run runIosDevice command is a shortcut for rm -rf www/* && webpack && cordova run ios --device.
1
2
3
| # Run iOS$ npm run runIosEmulator$ npm run runIosDevice |
Conclusion
With this tutorial, I've tried to show you how easy it is to create a hybrid, mobile application for your WordPress website. You should now be able to:
- create loosely coupled modules that respect CommonJS specs
- import CommonJS modules with ECMAScript 6
- use the WordPress REST API client side (with wp-api-angularjs)
- leverage Ionic Framework to create a great user interface
- use webpack to bundle your application
- use Cordova to run the application on iOS and Android
If you want to go further, then take a look at a project I created few months ago,WordPress Hybrid Client.
WordPress Hybrid Client
WordPress Hybrid Client (WPHC) is an open-source project available on GitHub that helps you to create iOS and Android versions of your WordPress website for free. WPHC is based on the same technology stack that we used in this tutorial.
WPHC includes the following features:
- push notifications
- bookmarks (offline mode)
- Google Analytics support
- automatic content updates
- social buttons
- accessibility (post font size)
- multiple languages (English, French, Chinese)
- infinite scroll
- syntax highlighter for tech blogs
- image caching
- app rating
Sign up here with your email



ConversionConversion EmoticonEmoticon