You asked for it. Then you asked for it again. Over and over we heard, "We want to be able to use Kendo UI Mobile with AngularJS".
Ask and you shall receive. Kendo UI Mobile support has landed in Angular Kendo UI.
As of the Q1 March release this year, the mobile widgets have been decoupled from the Kendo UI Mobile application framework so that they can now be used anywhere and with any framework. Given our work on the Angular Kendo UI project and your loud and clear demand, we figured Angular was the best place to start.
Oh - and now that Kendo UI Core (which includes Kendo UI Mobile) is open source and the Angular integrations have always been open source, you can use them for the very low price of "totes free" with no strings attached.
If you've worked with Kendo UI Mobile before, you're going to have change how you think just a tad when working with Angular. The Kendo UI Mobile Application object does a LOT of "behind the scenes" work for you such as adding meta tags, laying out visual elements and handling view state. Using the widgets outside of the Application object means that Angular will have to take over where Kendo UI leaves off. Well, Angular and your own mad coding skills.
You will need one of the mobile styles found in the Kendo UI styles/web folder, as well as jquery and kendo.ui.core. You will also need AngularJS and the Angular Kendo UI Integrations library. It's only one file called angular-kendo.js.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<!-- Makes your prototype chrome-less once bookmarked to your phone's home screen -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link href="app/components/kendo-ui-core/styles/web/kendo.bootstrap.mobile.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js"></script>
<script src="app/components/kendo-ui-core/src/js/kendo.ui.core.js"></script>
<script src="angular-kendo.js"></script>
</body>
</html>
We use the stylesheets for Kendo UI Mobile from the Web folder because we are not using the Kendo UI Mobile application object. We are only using the widgets. Kendo UI provides specific mobile styles to match the mobile widgets to the current theme. In this case, I've gone with the Kendo UI Bootstrap theme.
You can experiment with the themes to see the different colors they provide.
Any of the Kendo UI Mobile widgets can be used. For a complete set of demos, checkout the Kendo UI site. These widgets have the same directives as Kendo UI Web, except everything is prefixed with kendo-mobile
. For instance, if you wanted to create a NavBar, you would use the kendo-mobile-nav-bar
directive.
Don't forget to pass in the kendo.directives
module to your application module.
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<!-- Makes your prototype chrome-less once bookmarked to your phone's home screen -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link href="app/components/kendo-ui-core/styles/web/kendo.bootstrap.mobile.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<!-- NavBar -->
<header>
<div kendo-mobile-nav-bar>
<div kendo-mobile-view-title>CutePics</div>
</div>
</header>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js"></script>
<script src="app/components/kendo-ui-core/src/js/kendo.ui.core.js"></script>
<script src="angular-kendo.js"></script>
<script>
(function() {
var app = angular.module('App', [ 'kendo.directives' ]);
}());
</script>
</body>
</html>
Notice the meta
tags that I stuck in the head
. These are required for sites running on mobile devices as apps. They handle things like viewport scaling, zooming and other things that you want to "just work" on mobile. Kendo UI Mobile would do this for you ordinarilly, but using it outside of the Application object means we need to add them ourselves.
Which would give us...
That's a Kendo UI Mobile NavBar! It looks great, but it's got some padding around the top, left and right sides. Actually, the body is the one with the padding which we can remove with a little CSS and tweak the font to something a littler prettier while we're at it.
body, html {
padding: 0;
margin: 0;
font-family: Tahoma, sans-serif;
}
Much better! Now a Kendo UI NavBar is nice, but lets get to some more of the nitty gritty. Let's add in a Kendo UI Mobile ListView. I'm also going to impement a multipage scenario so we can put Angular through its paces here.
I'm going to pull in a feed from Reddit (don't worry! It's just the 'cute' reddit; absolutely SFW.) and drop thumbnails in a ListView. Tapping on one of the items will show the image in a new view full screen.
First, let's create the home.html
partial view.
<ul kendo-mobile-list-view k-data-source="reddit"
k-on-click="preview(kendoEvent)"
k-template="redditTemplate"></ul>
Now the preview.html
partial.
<img class="full" ng-src="{{ url }}" >
That gives us a ListView in the Home screen where we can put the initial feed from Reddit, and a second Preview screen where full screen images will be shown. We just need an `ng-view` in the index page to load our views.
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<!-- Makes your prototype chrome-less once bookmarked to your phone's home screen -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link href="app/components/kendo-ui-core/styles/web/kendo.blueopal.mobile.min.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div kendo-mobile-nav-bar>
<div kendo-mobile-view-title>CutePics</div>
<button kendo-mobile-button data-align="left" k-on-click="goBack()" ng-show="app.back">Back</button>
</div>
</header>
<div class="content" ng-view></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular-route.js"></script>
<script src="app/components/kendo-ui-core/src/js/kendo.ui.core.js"></script>
<script src="angular-kendo.js"></script>
<script src="scripts.js"></script>
</body>
</html>
We're finally able to write some Angular code. We're going to be needing a few things...
(function () {
var app = angular.module("myApp", ["ngRoute", "kendo.directives"]);
app.controller('HomeCtrl', [ '$scope', function ($scope) {
// home code
}]);
app.controller('PreviewCtrl', [ '$scope', function ($scope, $routeParams, appSvc) {
// preview code
}]);
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
}).
when('/preview', {
templateUrl: 'partials/preview.html',
controller: 'PreviewCtrl'
})
}]);
}());
That sets up our basic application structure. The Kendo UI Mobile ListView in the home.html
file is referencing a few things on the scope in the Home controller that don't exist.
The first attribute is a Kendo UI DataSource. Kendo UI Widgets are tightly coupled to the concept of a Kendo UI DataSource. DataSources can be used in Angular without any issue and should be used in place of $http
when possible since widgets are not aware of changes everywhere, but only changes that occur inside of certain Kendo UI constructs like the DataSource.
The k-template
defines the template we want to use for each item. Kendo UI has its own powerful templating system that Kendo UI widgets will expect to be using.
The k-on-click
defines a method that should occur when one of the items in the list gets clicked on. In this case, we want to navigate to the "/preview" route manually.
We also need a service to hold a reference to the currently selected item. This way both the Home and Preview controllers can access the same data and the scopes will stay up to date. This is a lot, so lets start with the application service which will hold the currently selected item. By default it's null.
// appSvc provides shared access between home and preview
app.service('appSvc', function () {
this.selectedItem = null;
});
In the Home controller, we need to define the DataSource, the template and the click event.
app.controller('HomeCtrl', [ '$scope', '$location', 'appSvc', function
$scope, $location, appSvc) {
// the remote data source. it is read automatically when the listview
// is initialized by the kendo-mobile-list-view directive
$scope.reddit = new kendo.data.DataSource({
transport: {
read: 'http://www.reddit.com/r/cute.json',
type: 'jsonp'
},
schema: {
// doing a little filtering here to ensure we only look at images and not
// videos or links to pages which contain images
parse: function (data) {
var data = $.map(data.data.children, function (item) {
if (item.data.url.indexOf('.jpg') > -1) {
return item;
}
})
return data;
}
}
}),
// the template for each item in the dataset
$scope.redditTemplate = "<a class='reddit-item'><img src='#: data.thumbnail #'><p>#: data.title #</p></a>";
$scope.preview = function(e) {
// set the selected item on the service so we can access it from preview partial
appSvc.selectedItem = e.dataItem.data;
// manually navigate to the preview partial
$location.path("/preview")
}
}]);
The preview controller only needs the application service injected in. The ng-source
binding in the "preview.html" partial will update when the application service updates.
app.controller('PreviewCtrl', [ '$scope', '$routeParams', 'appSvc', function ($scope, $routeParams, appSvc) {
$scope.url = appSvc.selectedItem.url;
}]);
A little CSS and we're getting close to wrapping up.
body, html {
padding: 0;
margin: 0;
font-family: Tahoma, sans-serif;
}
.reddit-item {
height: 100%;
overflow: scroll;
}
.reddit-item p {
color: black;
}
.reddit-item img {
float: left;
margin-right: 20px;
}
.full {
width: 100%;
height: auto;
}
Now the ListView displays the data. Clicking an item in the list will get us to the Preview screen with a full image.
Wonderful! And what an adorable seal.
HOWEVER. We have a bit of a problem in that we can't go back to the previous page. We need to add a "Back" button in the NavBar. That button only needs to be visible on the Preview page. Instead of putting a NavBar on both pages, lets create a LayoutController which will expose the Application Service to the main layout and allow us to toggle the visibility of the button.
Here is the complete HTML for the application layout.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<!-- Makes your prototype chrome-less once bookmarked to your phone's home screen -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link href="app/components/kendo-ui-core/styles/web/kendo.blueopal.mobile.min.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="style.css">
</head>
<body ng-controller="LayoutCtrl">
<header>
<div kendo-mobile-nav-bar>
<div kendo-mobile-view-title>CutePics</div>
<button kendo-mobile-button data-align="left" k-on-click="goBack()" ng-show="app.back">Back</button>
</div>
</header>
<div class="content" ng-view></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular-route.js"></script>
<script src="app/components/kendo-ui-core/src/js/kendo.ui.core.js"></script>
<script src="angular-kendo.js"></script>
<script src="scripts.js"></script>
</body>
</html>
...And the complete JavaScript for the entire application
(function () {
var app = angular.module("App", ["ngRoute", "kendo.directives"]);
// appSvc provides items
app.service('appSvc', function () {
this.selectedItem = null;
this.back = false;
});
app.controller('LayoutCtrl', [ '$scope', '$location', 'appSvc', function ($scope, $location, appSvc) {
// expose the service
$scope.appSvc = appSvc;
// handle back button click
$scope.goBack = function () {
appSvc.back = false;
$location.path('/');
}
}]);
app.controller('HomeCtrl', [ '$scope', '$location', 'appSvc', function ($scope, $location, appSvc) {
// the remote data source. it is read automatically when the listview
// is initialized by the kendo-mobile-list-view directive
$scope.reddit = new kendo.data.DataSource({
transport: {
read: 'http://www.reddit.com/r/cute.json',
type: 'jsonp'
},
schema: {
// doing a little filtering here to ensure we only look at images and not
// videos or links to pages which contain images
parse: function (data) {
var data = $.map(data.data.children, function (item) {
if (item.data.url.indexOf('.jpg') > -1) {
return item;
}
})
return data;
}
}
}),
// the template for each item in the dataset
$scope.redditTemplate = "<a class='reddit-item'><img src='#: data.thumbnail #'><p>#: data.title #</p></a>";
$scope.preview = function(e) {
// set the selected item on the service so we can access it from preview partial
appSvc.selectedItem = e.dataItem.data;
// manually navigate to the preview partial
$location.path("/preview")
// set the back button to visible
appSvc.back = true;
}
}]);
app.controller('PreviewCtrl', [ '$scope', '$routeParams', 'appSvc', function ($scope, $routeParams, appSvc) {
// set the image url
$scope.url = appSvc.selectedItem.url;
}]);
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
}).
when('/preview', {
templateUrl: 'partials/preview.html',
controller: 'PreviewCtrl'
})
}]);
}());
You may have noticed that the NavBar is not fixed, as in it scrolls off the screen as the list scrolls. This is another thing Kendo UI Mobile usually takes care of. It's not hard to do with a little CSS wizardry and some fixed positioning. Remember, when in doubt about CSS, go look at how Bootstrap does it! Since fixing the header takes it out of the document layout, the body will now appear underneath it. We need to add some padding to the content area to account for the fixed header.
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100px;
width: 100%;
z-index: 1030;
}
.content {
padding-top: 50px;
height: 100%;
}
The z-index makes sure all the content scrolls UNDER the header while the header stays put.
We are so pleased to release this initial implementation of the Kendo UI Mobile widgets for Angular. If you are working with us on this project (and many of you are), then you know that this is the single most active community project right now at Kendo UI. If you find a bug, have an idea for a feature or want to make an improvement, hit us up on GitHub. Make sure you include a working demonstration of your issue! I like to use Plunker with Angular Kendo UI, but we aren't picky!
Grab a copy of Kendo UI Core (it's free!) and get started building mobile apps with AngularJS. Like I've always said, a great JavaScript framework deserves a great UI. That's why Angular and Kendo UI Mobile are the perfect match.
Burke Holland is a web developer living in Nashville, TN and was the Director of Developer Relations at Progress. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke worked for Progress as a Developer Advocate focusing on Kendo UI.