The view control
In the last blog we finished the service layer. It's time now to start our UI! Let us add a new WebuserControl View.ascx to the root folder of our project:
Code Behind
After adding the control, we have to change some things in the codebehind file:
Original:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Angularmodule
{
public partial class View : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
The first thing to do is changing the base class of our WebUserControl to a DNN module base class (needs using DotNetNuke.Entities.Modules also):
public partial class View : PortalModuleBase
In the Page_Load event, we insert some code to include the DNN antiforgery and ajax support (needs using DotNetNuke.Services.Exceptions also):
protected void Page_Load(object sender, EventArgs e)
{
try
{
DotNetNuke.Framework.ServicesFramework.Instance.RequestAjaxScriptSupport();
DotNetNuke.Framework.ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
}
catch (Exception exc) //Module failed to load
{
Exceptions.ProcessModuleLoadException(this, exc);
}
}
Normally this is all you need in the codebehind file. But to keep some things simple later we add some more properties to this file helping us with localization, settings and others:
Users
We need a list of all users with username and userid to interactively assign an item to an user. (This could also be done with an WebApi call. But it is easier to include this lookup list data at page generation time so we do not have to do an extra ajax roundtrip for this). The user list is generated as json array string
protected string Users
{
get
{
var users = UserController.GetUsers(PortalId).Cast<UserInfo>()
.Select(u => new { text = u.Username, id = u.UserID });
return ClientAPI.GetSafeJSString(JsonConvert.SerializeObject(users));
}
}
additional usings to DotNetNuke.Entities.Users, DotNetNuke.UI.Utilities and Newtonsoft.Json are needed
Resources
To be able to use static content localization, we use somethings similar. To hold localized strings for our application we make use of the .NET resource files. Add a special folder App_LocalResources to your project and inside this folder add a new RESX file with the same name as your UserControl: View.ascx.resx. Additional languages are simply added by View.ascx.de-DE.resx etc. In the resx file(s) you can enter a key and the corresponding localized string for every language you want to support. Please make use of the standard DNN syntax for the keys (e.g. cmdSave.Text for the text property of a commandbutton or EmptyField.Error for an error Message). Unfortunately we need an extra using to System.Resources from the System.Windows.Forms reference here. Hope that in later versions of DNN this is not needed because something similar is implemented in the core...
protected string Resources
{
get
{
using (var rsxr = new ResXResourceReader(MapPath(LocalResourceFile + ".ascx.resx")))
{
var res = rsxr.OfType<DictionaryEntry>()
.ToDictionary(
entry => entry.Key.ToString().Replace(".", "_"),
entry => LocalizeString(entry.Key.ToString()));
return ClientAPI.GetSafeJSString(JsonConvert.SerializeObject(res));
}
}
}
ModuleSettings
If we need informations of the module settings it is very handy to have them also onboard:
protected string ModuleSettings
{
get
{
return ClientAPI.GetSafeJSString(JsonConvert.SerializeObject(Settings));
}
}
Editable
If someone is logged in who has edit rights to the module and when the module is in EditMode we want to show some additional UI elements:
protected bool Editable
{
get { return IsEditable && EditMode; }
}
needs additional usings to DotNetNuke.Security and DotNetNuke.Security.Permissions
The View
Including Javascript and CSS files
Time to add our references to the angular libs and other needed javascript and css stuff. As mentioned in Part 1 of this blog series, we do not include the needed libs directly in our module. Instead we only reference the installed libraries in DNN:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="View.ascx.cs" Inherits="Angularmodule.View" %>
<%@ Register TagPrefix="dnn" TagName="JavaScriptLibraryInclude" Src="~/admin/Skins/JavaScriptLibraryInclude.ascx" %>
<%@ Register TagPrefix="dnn" Namespace="DotNetNuke.Web.Client.ClientResourceManagement" Assembly="DotNetNuke.Web.Client" %>
<dnn:JavaScriptLibraryInclude runat="server" Name="AngularJS" />
<dnn:JavaScriptLibraryInclude runat="server" Name="angular-route" />
<dnn:JavaScriptLibraryInclude runat="server" Name="angular-ng-progress" />
<dnn:DnnCssInclude runat="server" FilePath="~/Resources/libraries/angular-ng-progress/01_00_07/ngProgress.min.css" />
<dnn:JavaScriptLibraryInclude runat="server" Name="angular-ui-sortable" />
<dnn:JavaScriptLibraryInclude runat="server" Name="angular-ng-dialog" />
<dnn:DnnCssInclude runat="server" FilePath="~/Resources/libraries/angular-ng-dialog/00_05_01/ngDialog.min.css" />
<dnn:DnnCssInclude runat="server" FilePath="~/Resources/libraries/angular-ng-dialog/00_05_01/ngDialog-theme-default.min.css" />
Unfortunately there is nothing similar for CSS like for Javascript files, so we need to reference the css files with their installed path instead of only using the library name even if the css files are included in the library package.
If you do not want to build the appropriate Javascript packages by yourself (see Part 1 of this blog series) you can download the needed Angular Libraries InstallPackage from here!
The HTML
The HTML part is very short. The div with the attribute ng-app is the place where all the magic happens later. Important is the fact that the outer div has a unique id (we use the moduleID) so that we can bootstrap Angular later with this ID. In a normal Angular App outside of DNN this is not needed. But in an environment where two or more instances of the same module could reside on the same page this is very important to keep the data apart!
<div id="itemApp<%=ModuleId%>" class="itemApp">
<div ng-view>Loading...</div>
</div>
The Bootstrapping script
When the loading of the page is ready, the Angular app will be initialized. Because we need to send an Antiforgery token together with TabId and ModuleId in the header of each WebApi request (remember the [ValidateAntiForgeryToken] of your WebApi methods?) we preconfigure the Angular $httpProvider with these DNN specific headers.
After initialization we add the properties from our codebehind file (View.ascx.cs) to the app and bootstrap angular on the outer div of our html part.
<script>
angular.element(document).ready(function () {
function init(appName, moduleId, apiPath) {
var sf = $.ServicesFramework(moduleId);
var localAppName = appName + moduleId;
var application = angular.module(localAppName, [appName])
.constant("serviceRoot", sf.getServiceRoot(apiPath))
.config(function($httpProvider) {
var httpHeaders = { "ModuleId": sf.getModuleId(), "TabId": sf.getTabId(), "RequestVerificationToken": sf.getAntiForgeryValue() };
angular.extend($httpProvider.defaults.headers.common, httpHeaders);
});
return application;
};
var app = init("itemApp", <%=ModuleId%>, "Angularmodule");
app.constant("userlist", "<%=Users%>");
app.constant("resources", "<%=Resources%>");
app.constant("editable", "<%=Editable%>");
app.constant("moduleId", "<%=ModuleId%>");
app.constant("settings", "<%=ModuleSettings%>");
var moduleContainer = document.getElementById("itemApp<%=ModuleId%>");
angular.bootstrap(moduleContainer, [app.name]);
});
</script>
Lets run !
There are only few steps before we can run our angular app.
The first thing: Build your application now!
At the moment our module is located in the DNN installation files but DNN does not know about it. So we need to register our module first. Login as a superuser and navigate to Host-Extensions. CLick the last button on top Create new Module. Fill out the form you see in the image and click OK:
Now the module is registered in the system and you can add it to a page. (I think you know how this works. If not: see some basic videos on youtube about DNN!)
Normally the module should look like this in the page:
as we see, nothing happens! When we take a look in the browser developer tools we see an error:
Clicking on the error opens an error page in the browser:
This one does not help because its somewhere in Angular. We first have to click us through by clicking the blue link 3 or 4 times to get to the real error:
OK: module 'itemApp' is not available. Yeah thats right. We haven't created a Javascript Angular App yet. But that is something for the next part of my blog series!