Read More on Telerik Blogs
October 08, 2013 Mobile
Get A Free Trial

In an earlier post we covered the four project templates that are included with the Icenium Extension for Visual Studio, and also linked to a set of alternative templates that are identical to the "official" ones that ship with the extension, minus the sample app boilerplate. Today, though, I want to cover what your options when you'd like to create your own custom project templates for building hybrid mobile apps with Icenium in Visual Studio.

Why would this be useful? Well - perhaps you use Angular.js or Backbone.js in your hybrid mobile projects, and you'd really love to have a starting point that includes the assets you need for you project, and perhaps even some basic configuration, app boilerplate and directory structure in place for you. Creating your own custom hybrid mobile template isn't hard - it just involves a little bit of effort. So where do we start?

1.) Pick Your Base

It's a good idea to start with one of the existing project templates - since a lot of what they contain will be important for you to have. For the sake of this walkthrough, we're going to start with the built-in "Kendo UI Mobile" project template that ships with the Icenium Extension for Visual Studio. For my custom project template, I plan to use Kendo UI Mobile, along with a couple of other libraries I want to have on hand: postal.js and machina.js.

2.) Find and Copy the Existing Project Template

A Visual Studio Extension (a .vsix file) is effectively a zip archive. In fact, you can view the contents of an extension if you rename the file extension to ".zip" instead of ".vsix".

In the screenshot sequence below, I renamed the extension to ".zip", extracted it, and then browsed the extracted files, to the "Project Templates" directory (where the four out-of-the-box templates can be found).

At this point, you need to extract the desired zip file into a working directory somewhere else on your file system. For example, I've extracted the Telerik.Mobile.Cordova.KendoUI.zip file to a different directory and can see the following contents:

Now that I have this project template extracted, I can begin to modify the contents as needed.

3.) Modify the .vstemplate File

The .vstemplate file contains information about the project structure and resources, which Visual Studio will depend on to properly create a new project based on this template. We're going to make the following changes to this file:

  • Change the Name element's value to "Kendo UI Mobile (w/postal and machina)".
  • Change the Description element.
  • Change the TemplateID element's value to "KendoUI.postal_machina".
  • Remove the Folder element from the original vstemplate file that created the "data" folder and added "weather.json" to it.
  • Under the Folder element containing the assets for the images directory, remove the location, search and weather images.
  • Under the Folder element containing the assets for the scripts directory, remove the location, login and weather files.
  • Under the Folder element for the "scripts" directory, add items for machina.js and postal.js, as well as underscore.js (underscore is a dependency of both machina and postal).

When the above changes have been applied, the .vstemplate file will look like this:

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
<TemplateData>
<Name>Kendo UI Mobile (w/postal and machina)</Name>
<Description>Project template for creating hybrid mobile applications using Kendo UI Mobile, as well as machina.js and postal.js.</Description>
<Icon>Icon.ico</Icon>
<TemplateID>KendoUI.postal_machina</TemplateID>
<ProjectType>Icenium</ProjectType>
<SortOrder>20</SortOrder>
<NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp>
<CreateNewFolder>true</CreateNewFolder>
<DefaultName>MobileProject</DefaultName>
<ProvideDefaultName>true</ProvideDefaultName>
</TemplateData>
<TemplateContent>
<CustomParameters>
<CustomParameter Name="$isrunnable$" Value="true" />
</CustomParameters>
<Project File="mobile.iceproj" ReplaceParameters="true">
<Folder Name="App_Resources" TargetFolderName="App_Resources">
<Folder Name="iOS" TargetFolderName="iOS">
<ProjectItem ReplaceParameters="false" TargetFileName="icon.png">icon.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon@2x.png">icon@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-72.png">icon-72.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-72@2x.png">icon-72@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default.png">Default.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default@2x.png">Default@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default-568h@2x.png">Default-568h@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default-Portrait.png">Default-Portrait.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default-Landscape.png">Default-Landscape.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default-Portrait@2x.png">Default-Portrait@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Default-Landscape@2x.png">Default-Landscape@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Icon-Small.png">icon-small.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Icon-Small@2x.png">icon-small@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Icon-Small-50.png">Icon-Small-50.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="Icon-Small-50@2x.png">Icon-Small-50@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-40.png">icon-40.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-40@2x.png">icon-40@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-60.png">icon-60.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-60@2x.png">icon-60@2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-76.png">icon-76.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icon-76@2x.png">icon-76@2x.png</ProjectItem>
</Folder>
<Folder Name="Android" TargetFolderName="Android">
<Folder Name="drawable-hdpi" TargetFolderName="drawable-hdpi">
<ProjectItem ReplaceParameters="false" TargetFileName="icon.png">icon.png</ProjectItem>
</Folder>
<Folder Name="drawable-mdpi" TargetFolderName="drawable-mdpi">
<ProjectItem ReplaceParameters="false" TargetFileName="icon.png">icon.png</ProjectItem>
</Folder>
<Folder Name="drawable-ldpi" TargetFolderName="drawable-ldpi">
<ProjectItem ReplaceParameters="false" TargetFileName="icon.png">icon.png</ProjectItem>
</Folder>
<Folder Name="drawable-nodpi" TargetFolderName="drawable-nodpi">
<ProjectItem ReplaceParameters="false" TargetFileName="splashscreen.9.png">splashscreen.9.png</ProjectItem>
</Folder>
</Folder>
</Folder>
<Folder Name="kendo" TargetFolderName="kendo">
<Folder Name="js" TargetFolderName="js">
<ProjectItem ReplaceParameters="false" TargetFileName="jquery.min.js">jquery.min.js</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="kendo.mobile.min.js">kendo.mobile.min.js</ProjectItem>
</Folder>
<Folder Name="styles" TargetFolderName="styles">
<Folder Name="images" TargetFolderName="images">
<ProjectItem ReplaceParameters="false" TargetFileName="back.png">back.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="KendoUI.ttf">KendoUI.ttf</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="KendoUI.woff">KendoUI.woff</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="wp8_icons.png">wp8_icons.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="wp8_inverseicons.png">wp8_inverseicons.png</ProjectItem>
</Folder>
<ProjectItem ReplaceParameters="false" TargetFileName="kendo.mobile.all.min.css">kendo.mobile.all.min.css</ProjectItem>
</Folder>
</Folder>
<Folder Name="styles" TargetFolderName="styles">
<ProjectItem ReplaceParameters="false" TargetFileName="main.css">main.css</ProjectItem>
<Folder Name="images" TargetFolderName="images">
<ProjectItem ReplaceParameters="false" TargetFileName="icenium.png">icenium.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="icenium2x.png">icenium2x.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="logo.png">logo.png</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="logo2x.png">logo2x.png</ProjectItem>
</Folder>
</Folder>
<Folder Name="scripts" TargetFolderName="scripts">
<ProjectItem ReplaceParameters="false" TargetFileName="app.js">app.js</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="underscore.min.js">underscore.min.js</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="machina.min.js">machina.min.js</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="postal.min.js">postal.min.js</ProjectItem>
</Folder>
<ProjectItem ReplaceParameters="false" OpenInEditor="true">index.html</ProjectItem>
<ProjectItem ReplaceParameters="false" OpenInEditor="false">cordova.android.js</ProjectItem>
<ProjectItem ReplaceParameters="false" OpenInEditor="false">cordova.ios.js</ProjectItem>
</Project>
</TemplateContent>
</VSTemplate>

4.) Modify the mobile.iceproj File

The mobile.iceproj file is used to determine what assets are sent to Icenium's cloud build services (Content and Resource items are packaged and sent to the cloud, and project properties are pesisted here as well). It's important that we update this file to reflect the contents of our template, as we did with the .vstemplate file. We'll make the following changes:

  • Remove the Content includes for:
    • weather.js
    • location.js
    • login.js
    • weather.json
    • location-related images
    • weather-related images
    • search-related images
  • Add Content includes for:
    • underscore.js
    • machina.js
    • postal.js

When we're done with the above steps, our mobile.iceproj file will look like this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>$guid1$</ProjectGuid>
<ProjectTypeGuids>{070BCB52-5A75-4F8C-A973-144AF0EAFCC9}</ProjectTypeGuids>
<AppIdentifier>com.telerik.$safeappidentifier$</AppIdentifier>
<ProjectName>$projectname$</ProjectName>
<IsRunnable>$isrunnable$</IsRunnable>
<FrameworkVersion>3.0.0</FrameworkVersion>
<AndroidPermissions>
android.permission.CAMERA;android.permission.VIBRATE;android.permission.ACCESS_COARSE_LOCATION;android.permission.ACCESS_FINE_LOCATION;android.permission.ACCESS_LOCATION_EXTRA_COMMANDS;android.permission.READ_PHONE_STATE;android.permission.INTERNET;android.permission.RECEIVE_SMS;android.permission.RECORD_AUDIO;android.permission.MODIFY_AUDIO_SETTINGS;android.permission.READ_CONTACTS;android.permission.WRITE_CONTACTS;android.permission.WRITE_EXTERNAL_STORAGE;android.permission.ACCESS_NETWORK_STATE;android.permission.ACCOUNT_MANAGER;android.permission.GET_ACCOUNTS;android.permission.MANAGE_ACCOUNTS;android.permission.BROADCAST_STICKY
</AndroidPermissions>
<CorePlugins>
org.apache.cordova.core.device;org.apache.cordova.core.AudioHandler;org.apache.cordova.core.battery-status;org.apache.cordova.core.camera;org.apache.cordova.core.console;org.apache.cordova.core.contacts;org.apache.cordova.core.device;org.apache.cordova.core.device-motion;org.apache.cordova.core.device-orientation;org.apache.cordova.core.dialogs;org.apache.cordova.core.file;org.apache.cordova.core.file-transfer;org.apache.cordova.core.geolocation;org.apache.cordova.core.globalization;org.apache.cordova.core.inappbrowser;org.apache.cordova.core.media-capture;org.apache.cordova.core.network-information;org.apache.cordova.core.splashscreen;org.apache.cordova.core.vibration
</CorePlugins>
<DeviceOrientations>Portrait;Landscape</DeviceOrientations>
<iOSStatusBarStyle>Default</iOSStatusBarStyle>
<AndroidHardwareAcceleration>false</AndroidHardwareAcceleration>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
</PropertyGroup>

<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>bin\Debug\$(DevicePlatform)\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>bin\Release\$(DevicePlatform)\</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>

<ItemGroup>
<Resource Include="App_Resources\iOS\icon.png"/>
<Resource Include="App_Resources\iOS\icon@2x.png"/>
<Resource Include="App_Resources\iOS\icon-72.png"/>
<Resource Include="App_Resources\iOS\icon-72@2x.png"/>
<Resource Include="App_Resources\iOS\Icon-Small.png"/>
<Resource Include="App_Resources\iOS\Icon-Small@2x.png"/>
<Resource Include="App_Resources\iOS\Icon-Small-50.png"/>
<Resource Include="App_Resources\iOS\Icon-Small-50@2x.png"/>
<Resource Include="App_Resources\iOS\Default.png"/>
<Resource Include="App_Resources\iOS\Default@2x.png"/>
<Resource Include="App_Resources\iOS\Default-568h@2x.png"/>
<Resource Include="App_Resources\iOS\Default-Portrait.png"/>
<Resource Include="App_Resources\iOS\Default-Landscape.png"/>
<Resource Include="App_Resources\iOS\Default-Portrait@2x.png"/>
<Resource Include="App_Resources\iOS\Default-Landscape@2x.png"/>
<Resource Include="App_Resources\iOS\icon-40.png"/>
<Resource Include="App_Resources\iOS\icon-40@2x.png"/>
<Resource Include="App_Resources\iOS\icon-60.png"/>
<Resource Include="App_Resources\iOS\icon-60@2x.png"/>
<Resource Include="App_Resources\iOS\icon-76.png"/>
<Resource Include="App_Resources\iOS\icon-76@2x.png"/>
</ItemGroup>
<ItemGroup>
<Resource Include="App_Resources\Android\drawable-hdpi\icon.png"/>
<Resource Include="App_Resources\Android\drawable-mdpi\icon.png"/>
<Resource Include="App_Resources\Android\drawable-ldpi\icon.png"/>
<Resource Include="App_Resources\Android\drawable-nodpi\splashscreen.9.png"/>
</ItemGroup>
<ItemGroup>
<Content Include="index.html"/>
<Content Include="kendo\js\jquery.min.js"/>
<Content Include="kendo\js\kendo.mobile.min.js"/>
<Content Include="kendo\styles\images\back.png"/>
<Content Include="kendo\styles\images\KendoUI.ttf"/>
<Content Include="kendo\styles\images\KendoUI.woff"/>
<Content Include="kendo\styles\images\wp8_icons.png"/>
<Content Include="kendo\styles\images\wp8_inverseicons.png"/>
<Content Include="kendo\styles\kendo.mobile.all.min.css"/>
<Content Include="scripts\app.js"/>
<Content Include="scripts\undescore.js"/>
<Content Include="scripts\machina.min.js"/>
<Content Include="scripts\postal.min.js"/>
<Content Include="styles\images\icenium.png"/>
<Content Include="styles\images\icenium2x.png"/>
<Content Include="styles\images\logo.png"/>
<Content Include="styles\images\logo2x.png"/>
<Content Include="styles\main.css"/>
<Content Include="cordova.ios.js">
<Platform>iOS</Platform>
<TargetName>cordova.js</TargetName>
</Content>
<Content Include="cordova.android.js">
<Platform>Android</Platform>
<TargetName>cordova.js</TargetName>
</Content>
</ItemGroup>

<Import Project="$(IceniumTargets)Build.targets"/>

<!--
TODO: Uncomment this code if you need to customize the build process.
<Target Name="BeforeBuild">
<Message Text="TODO: Implement BeforeBuild" Importance="high" />
</Target>

<Target Name="AfterBuild">
<Message Text="TODO: Implement AfterBuild" Importance="high" />
</Target>
-->
</Project>

5.) Remove Unwanted Assets

So far we've only edited the .vstemplate and .iceproj files - and that's fine. However, there are files inside our directory structure that need to be removed. That being the case, the next steps are:

  • remove the data directory
  • remove scripts/weather.js
  • remove scripts/location.js
  • remove scripts/login.js

We don't need these file anymore, and wouldn't want them showing up in the directory structure of a new project on the file system.

6.) Edit Existing Assets

Since we've started with the Icenium Kendo UI Mobile project template as a base, this means our index.html, main.css and app.js files have sample app boilerplate that we need to remove.

index.html

Let's take the index.html file first. I removed all the sample-app-related boilerplate, so that it contains only this:

<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<link href="kendo/styles/kendo.mobile.all.min.css" rel="stylesheet" />
<link href="styles/main.css" rel="stylesheet" />
</head>
<body>

<!--Layout-->
<div data-role="layout" data-id="tabstrip-layout" >

<!--Header-->
<div data-role="header">

</div>

<!--Footer-->
<div data-role="footer">

</div>
</div>

<script src="cordova.js"></script>
<script src="kendo/js/jquery.min.js"></script>
<script src="kendo/js/kendo.mobile.min.js"></script>
<script src="scripts/underscore.min.js"></script>
<script src="scripts/machina.js"></script>
<script src="scripts/postal.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>

You'll notice that I left part of the "layout" markup in place - since I anticipate using it in nearly all of my apps. I'll just have to include the specifics of what goes in the header and footer (which, understandably, will vary per application). I also moved the script include to the end of the body tag, and added script tags for underscore, machina.js and postal.js.

app.js

Next, with our app.js file, I removed all sample-app-related boilerplate, so it contains the following:

(function (global) {
    var app = global.app = global.app || {};

    document.addEventListener("deviceready", function () {
        app.application = new kendo.mobile.Application(document.body, { layout: "tabstrip-layout" });
    }, false);
})(window);

main.css

For my template, I'd like for an empty stylesheet to be included, so I removed all the CSS from styles/main.css, but left the file itself in place.

7.) Add New Assets

We need to be sure and physically add underscore, machina and postal to our scripts directory, or else our project template would be embarassingly broken:

8.) Make Your Project Template Available to Visual Studio

OK, great! At this point we've updated the .vstemplate and .iceproj files, we've removed all the assets we don't need, we've modified the existing index.html, main.css and app.js files, and we've added our new dependencies to the scripts directory. All we need to do now is move this folder structure somewhere it can be recognized by Visual Studio. We can copy/move the folder containing our project template to the %USERPROFILE%\My Documents\Visual Studio 2012\Templates\ProjectTemplates folder. Once you've done this, close Visual Studio if it happens to be open, and then re-lauch it. Once Visual Studio is open, go to File -> New Project, and you'll see our new project template listed under the "Icenium" project template list:

9.) (Optional) Deploy Your Project Template To Visual Studio Gallery

You might find that others are interested in project templates you've created. There's a much better way to share them than simply giving someone a zip of your project template directory and sending it to them (though that's certainly a valid way to do it as well). If you follow the steps of this article, you'll get an idea of how you can create a Visual Studio Extension project, and ultimately create an extension that installs your custom project template. You can deploy that extension to the Visual Studio Gallery site, giving others access to your custom template(s). (Be sure you have the Visual Studio 2012 SDK installed before you start down this path.)


About the Author

Jim Cowart

Jim Cowart is an architect, developer, open source author, and overall web/hybrid mobile development geek. He is an active speaker and writer, with a passion for elevating developer knowledge of patterns and helpful frameworks.