Laravel User Authentication with Ajax Validation

This post documents how to add Ajax form validation to Laravel User Authentication that is generated by the Artisan console.

Requirements

An instance of Laravel 5.2 setup for local development is required. Head over to the Laravel 5.2 docs to get setup as needed. For my setup on Windows 10, I am using XAMPP 5.6.12 which meets all of the requirements. Note that your document root will need to be the laravel/public directory. Here is the virtual hosts configuration I am using for Laravel.

httpd-vhosts.conf
<VirtualHost *:8080>
    DocumentRoot "C:/xampp/htdocs/laravel/public"
    ServerName laravel.dev
    ServerAlias www.laravel.dev
    SetEnv APPLICATION_ENV development
    <Directory "C:/xampp/htdocs/laravel">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

More info on setting up XAMPP for Windows

New Laravel Site

Using Composer, install Laravel 5.2 into htdocs/laravel

cd c:/xampp/htdocs

composer create-project laravel/laravel laravel "5.2.*"
Generate Authentication Scaffolding
cd laravel

php artisan make:auth

Now the Laravel site has routes and views to allow a user to register, login, logout, and reset their password. A HomeController has also been added to allow authenticated users to enter its view.

User Model

I prefer to organize models within their own directory. Therfore, let’s create a folder named Models in the app directory and move the User model into it.

mkdir app/Models

mv app/User.php app/Models

Edit app/Models/User.php, update the App namespace to App\Models.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    ...

  • Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, examine the source code.

Edit app/Http/Controllers/Auth/AuthController.php, update use App\User to use App\Models\User.

AuthController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    ...

Edit, config/auth.php, update the authentication drivers user provider.

'users' => [
    'driver' => 'eloquent',
    'model' => App\User::class,
],

REPLACE

'model' => App\User::class,

WITH

'model' => App\Models\User::class,

Database

Edit the .env configuration file to access the database. I have already created a database named laravel on the MySQL Server that is part of XAMPP.

.env
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

The settings above are the only ones I needed to update for my setup, I do not have a password set for MySQL Server and will just use the root account for local development. Now run php artisan migrate to create the tables for users.

Run Migration
php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table

Validation Rules

The validation rules are currently located in the AuthController, I would like to be able to share them with the new controller we will create later for ajax validation. Since these rules apply to the User, and that model is already being used by the AuthController, it seeems to be the best place for them.

Edit app/Models/User.php, adding the public function rules() to the class.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * Get the validation rules that apply to the user.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ];
    }

    ...

Edit app/Http/Controllers/Auth/AuthController.php. Locate the validator function and replace the array of rules being passed to the Validator make method with a call to the newly created rules method in our User model.

REPLACE

return Validator::make($data, [
    'name' => 'required|max:255',
    'email' => 'required|email|max:255|unique:users',
    'password' => 'required|min:6|confirmed',
]);

WITH

return Validator::make($data, (new User)->rules());

Before going further, now would be a good time to test registering a new user, login, logout etc.. to make sure everyting works. All we have done so far aside from the code that artisan generated is move the User model and move the validation rules from the AuthController into it.

The next page covers using the api middleware in routes, creating the Validation\UserController for handling the XHR request and finally updating the templates and adding the JavaScript for Ajax.


Animation

This code sample shows how to animate a background image using CSS.

HTML
<div class="banner-ani"></div>
CSS
.banner-ani {
    background-image: url(/sandbox/ui/assets/image/cool-dog-bg.png);
    background-color: transparent;
    background-position: 0px -196px;
    background-repeat:no-repeat;
    width: 230px;
    height: 196px;

    /* 
    after 2 seconds, provide a 1.5 second animation
    of the background image scrolling down from its
    pre-existing position of 0 -196px to 0 0

    css animation does not work in <= ie9 
    */
    -webkit-transition:all 1.5s ease;
    -moz-transition:all 1.5s ease;
    -webkit-animation: animatedBrandshopBannerBackground 1.5s 2s linear;
    -moz-animation: animatedBrandshopBannerBackground 1.5s 2s linear;
    animation: animatedBrandshopBannerBackground 1.5s 2s linear;

    -webkit-animation-fill-mode: forwards;
    -moz-animation-fill-mode: forwards;
    animation-fill-mode: forwards;
}
@keyframes animatedBrandshopBannerBackground {
    from { background-position: 0 -196px; }
    to { background-position: 0 0; }
}

demo


This code sample shows how to animate a background image using CSS and jQuery.

CSS
.banner-ani {
    background-image: url(/sandbox/ui/assets/image/cool-dog-bg.png);
    background-color: transparent;
    background-position: 0px -196px;
    background-repeat:no-repeat;
    width: 230px;
    height: 196px;
}
JavaScript & jQuery
$(function($){
    /*
    after 2 seconds, provide a 1.5 second animation
    of the background image scrolling down from its
    pre-existing position of 0 -196px to 0 0

    jquery plugin required: jquery.bgpos.js
    */
    var id,
        $bannerani = $('.banner-ani'),
        animate = function() {
            $bannerani.stop().animate({backgroundPosition:"(0 0)"}, {
                duration:1500,
                complete: function(){
                    console.log('Animation Completed');
                }
            });
        };

    clearTimeout(id);
    id = setTimeout(animate(), 2000);

});

demo

jQuery

A collection of useful jQuery code snippets

// get the value of a text input when modified
(function($){
    $('#sometextinput').on('change keyup paste', function() {
        console.log('VALUE', $(this).val());
    });
})(jQuery);

Window Resize

Wait for window resize to stop.

var resizeTimeout;
$(window).on('resize', function() {
    clearTimeout(resizeTimeout);
    resizeTimeout = setTimeout(doSomething(), 500);
});

If LoDash is present, you can use _.debounce instead.

$(window).on('resize', _.debounce(doSomething, 150));

with jQuery.proxy

$(window).on('resize', _.debounce( $.proxy( function() {
   // code
}, this), 150));

Vanilla JS Resize Handler

jQuery.extend

Use $.extend to merge objects.

var data = $.extend({}, {
    'artist': 'Ben Harper',
    'track': 'Gold To Me'},
    album, discography);

If LoDash is present, use _.extend instead.

var data = {
    'artist': 'Ben Harper',
    'track': 'Gold To Me'}

_.extend(data, album, discography);
jQuery.proxy

Use $.proxy on $.ajax callback to access this from lower scope

fetchData () {
    this.error = this.posts = null

    this.loading = true

    $.ajax({
        url: https://wordpress.example.com + '/wp/v2/posts'
    })
    .done( $.proxy( function( response ) {

        this.loading = false;
        this.posts = response;

    }, this ))
    .fail( $.proxy( function( response ) {

        this.loading = false;
        this.error = response;

    }, this ));
}

Various Ajax Error Responses

Use .ajaxSetup to add more precise ajax error handling.

$.ajaxSetup({
    error: function(jqXHR, exception) {
        if (jqXHR.status === 0) {
            alert('Not connect.\n Verify Network.');
        } else if (jqXHR.status == 404) {
            alert('Requested page not found. [404]');
        } else if (jqXHR.status == 500) {
            alert('Internal Server Error [500].');
        } else if (exception === 'parsererror') {
            alert('Requested JSON parse failed.');
        } else if (exception === 'timeout') {
            alert('Time out error.');
        } else if (exception === 'abort') {
            alert('Ajax request aborted.');
        } else {
            alert('Uncaught Error.\n' + jqXHR.responseText);
        }
    }
});

WordPress

// sticky header and footer
jQuery.noConflict();
    (function($) {
        // wait for resize to stop
        var resizeTimeout;
        $(window).on('resize', function() {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(setContentHeight, 500);
        });
        setContentHeight = function(event) {
            cVerticalMargin = $('.site-content').outerHeight(true)
                - $('.site-content').outerHeight();
            cVerticalBorder = $('.site-content').outerHeight()
                - $('.site-content').innerHeight();
            cVerticalPadding = $('.site-content').innerHeight()
                - $('.site-content').height();
            cHeight = $(window).height()
                - $('.site-header').outerHeight(true)
                - $('.site-footer').outerHeight(true)
                - cVerticalMargin
                - cVerticalBorder
                - cVerticalPadding;
            $('.site-content').css({'height': cHeight + 'px'});
        }
        setContentHeight();
})(jQuery);

Supporting Older and Newer jQuery

If you find yourself in a messy situation where you need to provide support for jQuery earlier than 1.7 on an intermittent basis.

// for those who use jquery earlier then 1.7 when there is no $.on exist yet
if (typeof jQuery.fn.on !== 'function') {
    jQuery.fn.on = jQuery.fn.live;
    jQuery.fn.off = jQuery.fn.die;
}

JavaScript Templating Options

Javascript templating is a technique to render JSON data in HTML markup with JavaScript.


Handlebars is a semantic web template system, started by Yehuda Katz in 2010. Handlebars.js is a superset of Mustache, and can render Mustache templates in addition to Handlebars templates.

The document body will need an element that the template will be injected into. Since the template in this example will be a list item for each artist, an unordered list element is the logical choice.

Template Target
<!-- template wrapper -->
<ul class="artist-list"></ul>
Template Source
<!-- template -->
<script id="tmpl-artist" type="text/tmpl">
    {{#each this}}
    <li data-artist-id="{{ArtistId}}">{{Name}}</li>
    {{/each}}
</script>
JavaScript
//jQuery and Handlebars
$(function() {
    var tmplSource = $('#tmpl-artist').html(),
        template = Handlebars.compile(tmplSource);

    $.getJSON('http://jimfrenette.com/chinook/api/artists').done(function(data) {
        if (data) {

            $(".artist-list").html(template(data));
        }
    });
});

A utility library delivering consistency, customization, performance, and extras.

Template Target (same as above)
Template Source
<!-- template -->
<script id="tmpl-artist" type="text/tmpl">
    <% _.forEach(data,function(artist) { %>
    <li data-artist-id="<%= artist.ArtistId %>"><%= artist.Name %></li>
    <% }); %>
</script>
JavaScript
//jQuery and Lo-Dash
$(function() {
    var tmplSource = $('#tmpl-artist').html(),
        template;

    $.getJSON('http://jimfrenette.com/chinook/api/artists').done(function(data) {
        if (data) {

            template = _.template(tmplSource)({
                data: data
            });

            $(".artist-list").html(template);
        }
    });
});

jQuery only templates

A jQuery only templating solution may be all that you need if none of the other libraries that provide templating such as lodash, underscore or handlebars is present.

Template Target (same as above)
Template Source
<!-- template -->
<script id="tmpl-artist" type="text/tmpl">
    <li id="artist_0"></li>
</script>
JavaScript
//jQuery
$(function() {
    var tmplSource = $('#tmpl-artist').html(),
        artistTmplItem;

    $.getJSON('http://jimfrenette.com/chinook/api/artists').done(function(data) {
        if (data) {

            for(var i = 0, len = data.length; i < len; i++) {

                artistTmpl = $(tmplSource);

                artistTmplItem = artistTmpl.attr('id','artist_'+data[i].ArtistId);

                artistTmplItem.text(data[i].Name);

                $('ul.artist-list').append(artistTmplItem);

            }
        }
    });
});

Just JavaScript

A JavaScript only solution that does not use jQuery or any other libraries.

Target element (same as Template Target above)
<ul class="artist-list"></ul>
JavaScript
var getJSON = function(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.responseType = 'json';
        xhr.onload = function() {
            var status = xhr.status;
            if (status == 200) {
                resolve(xhr.response);
            } else {
                reject(status);
            }
        };
        xhr.send();
    });
};

getJSON('http://jimfrenette.com/chinook/api/artists').then(function(data) {
    if (data) {
        // getElementsByClassName returns an array,
        // use the index [0] to get the first element match
        var artistList = document.getElementsByClassName('artist-list')[0],
            li = null,
            da = null;

        for(var i = 0, len = data.length; i < len; i++) {

            li = document.createElement('li');
            da = document.createAttribute('data-artist-id');
            da.value = data[i].ArtistId;
            li.setAttributeNode(da);
            li.appendChild(document.createTextNode(data[i].Name));
            artistList.appendChild(li);
        }
    }
});
Source Code

Benchmarks

Others ...

Resources

DNN Session Timer with AngularJS


This tutorial will guide you through the steps necessary to add a session timeout timer and alert dialog within a DotNetNuke (DNN) Module using AngularJS. Key concepts:

  • Using DNN server objects to create AngularJS client-side objects;
  • Using DNN jQuery and jQuery UI registration;
  • Using DNN Client Resource Management;
  • Using DNN Form Patterns

Requirements: DotNetNuke 7; AngularJS; An understanding of how to develop and install DotNetNuke modules. There are a number of resources dedicated to this topic, including my DotNetNuke 7 Module Templates blog post earlier this year.

DotNetNuke Page Setup

The Session Timer is designed to alert the user that they have been inactive for X number of minutes and their session is about to expire. They are presented with a dialog based on a threshold setting defined in a custom module setting to extend the session. Once the session has expired, the dialog content changes to notify the user of their expired session status. Further action will redirect the user to the login page to re-establish a new session. For this to work, your module will need to be placed on a page that is visible only to logged in users. You can check or modify the page permissions while logged in as admin or host at Admin > Page Management.

jQuery UI Registration

Our session alert will be using a jQuery UI modal dialog. Therefore we need to make sure that the jQuery and jQuery UI resources are loaded. jQuery and jQuery-UI are included with DNN. When registering jQuery-UI, it will register the dependent jQuery library as well. In you Module Control code behind, register the jQuery UI resources in the View module control code behind OnLoad or OnInit method:

View.ascx.cs
protected override void OnLoad(EventArgs e)
{
    DotNetNuke.Framework.jQuery.RequestUIRegistration();
}

AngularJS

Download AngularJS and place both angular.min.js and angular.js in a new folder named Library/Angular within your Module folder. For example, I have named my Module “Angular” so here is what the structure looks like in my Visual Studio Solution Explorer:

Suggested location of AngularJS library files within a DNN Module
Suggested location of AngularJS library files within a DNN Module

Create a file for our AngularJS JavaScript. For Visual Studio users, in Solution Explorer, right click on the Angular project, select Add > New Folder, name it “Scripts”. Now right click on the Scripts folder you just added and select Add > JavaScript file and name it SessionTimer.js. For now, add a single line at the top of this file to enable strict mode:

SessionTimer.js
'use strict';

We will use the DNN Client Resource Management API to load our angular.js and SessionTimer.js files. To enable this API in our module, we need to reference two assemblies in the root bin of our DNN website. Therefore, right click on the References folder and browse to the following dll files and reference them:

ClientDependency.Core.dll
DotNetNuke.Web.Client.dll

Add the following markup to the View module control to load the JavaScript resources. As a best practice, we are using the ForceProvider attribute with a value of DnnFormBottomProvider in our DnnJsInclude server controls to load these JavaScript resources at the bottom of the page. Additionally, we are going to use a Priority attribute to ensure they are loaded in the order listed.

View.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="View.ascx.cs" Inherits="DotNetNuke.Modules.Angular.View" %>
<%@ Register TagPrefix="dnn" Namespace="DotNetNuke.Web.Client.ClientResourceManagement" Assembly="DotNetNuke.Web.Client" %>

<dnn:DnnJsInclude ID="DnnAngularSessionInclude" runat="server" FilePath="~/DesktopModules/Angular/Scripts/SessionTimer.js" ForceProvider="DnnFormBottomProvider" Priority="200" />
<!-- In production, use minified version (angular.min.js) -->
<dnn:DnnJsInclude ID="DnnAngularInclude" runat="server" FilePath="~/DesktopModules/Angular/Library/Angular/angular.js" ForceProvider="DnnFormBottomProvider" Priority="210" />

Now add another line to the SessionTimer.js to define our AngularJS module:

SessionTimer.js
'use strict';

var sessionApp = angular.module('sessionApp', []);

DotNetNuke Module Settings

The Session Timer dialog needs a setting to tell it when to open. One way to store this value is to create a DNN Module Setting for it so it can be easily changed from within the CMS. If you used Christoc’s DotNetNuke Module Development Template to create your module, you should have a Settings module control stubbed out and ready for you to add the Session Timer Threshold setting controls so the value in minutes can be stored as a module setting in the DNN database. The Settings module control markup and code for this is below.

Settings.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Settings.ascx.cs" Inherits="DotNetNuke.Modules.Angular.Settings" %>
<%@ Register TagName="label" TagPrefix="dnn" Src="~/controls/labelcontrol.ascx" %>

<h2 id="dnnSitePanel-BasicSettings" class="dnnFormSectionHead"><a href="" class="dnnSectionExpanded"><%=LocalizeString("BasicSettings")%></a></h2>
<fieldset>
    <div class="dnnFormItem">
        <dnn:Label ID="lblSessionTimerThreshold" runat="server" /> 
        <asp:TextBox ID="txtSessionTimerThreshold" runat="server" />
    </div>
</fieldset>
Settings.ascx.cs
using System;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Services.Exceptions;

namespace DotNetNuke.Modules.Angular
{
    public partial class Settings : AngularModuleSettingsBase
    {
        #region Base Method Implementations

        /// -----------------------------------------------------------------------------
        /// 
        /// LoadSettings loads the settings from the Database and displays them
        /// 
        /// -----------------------------------------------------------------------------
        public override void LoadSettings()
        {
            try
            {
                if (Page.IsPostBack == false)
                {
                    if (Settings.Contains("SessionTimerThreshold"))
                        txtSessionTimerThreshold.Text = Settings["SessionTimerThreshold"].ToString();
                }
            }
            catch (Exception exc) //Module failed to load
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }

        /// -----------------------------------------------------------------------------
        /// 
        /// UpdateSettings saves the modified settings to the Database
        /// 
        /// -----------------------------------------------------------------------------
        public override void UpdateSettings()
        {
            try
            {
                var modules = new ModuleController();

                modules.UpdateModuleSetting(ModuleId, "SessionTimerThreshold", txtSessionTimerThreshold.Text);

            }
            catch (Exception exc) //Module failed to load
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }

        #endregion
    }
}

Settings.ascx.resx

To make use of the label control (dnn:Label) that is registered at the top of the Settings module control file, edit the respective resource (.resx) file as shown in the image below. The label control provides label text, and tool-tip text for additional information.

Settings.ascx.resx for our SessionTimer Threshold value in the Angular module settings form
Settings.ascx.resx for our SessionTimer Threshold value in the Angular module settings form

Add a SessionTimerJavaScript method to the AngularModuleBase class so all module controls in the Angular module have access to it. This method loads the SessionTimer.js and AngularJs module constants for the dialog display threshold and session duration. Here is what the file looks like with this method added.

AngularModuleBase.cs
using DotNetNuke.Entities.Modules;

namespace DotNetNuke.Modules.Angular
{
    public class AngularModuleBase : PortalModuleBase
    {

        public static void SessionTimerJavaScript(PortalModuleBase portalModuleBase)
        {
            if (portalModuleBase.Page != null)
            {
                string timerScript = "";

                int sessionWarningThresholdInMilliSeconds = int.Parse(portalModuleBase.Settings["SessionTimerThreshold"].ToString()) * 60000;
                int sessionDurationInMilliSeconds = portalModuleBase.Page.Session.Timeout * 60000;

                //guard against invalid warning threshold.
                if (sessionWarningThresholdInMilliSeconds >= sessionDurationInMilliSeconds)
                {
                    sessionWarningThresholdInMilliSeconds = sessionDurationInMilliSeconds - 1000;
                }

                string timerVars = string.Format("sessionApp.constant('settings',{{ threshold: {0}, sessionDuration: {1} }});\n", sessionWarningThresholdInMilliSeconds.ToString(), sessionDurationInMilliSeconds.ToString());

                if (!portalModuleBase.Page.ClientScript.IsStartupScriptRegistered("_SessionTimerInclude_"))
                {
                    portalModuleBase.Page.ClientScript.RegisterStartupScript(portalModuleBase.Page.GetType(), "_SessionTimerInclude_", timerScript, false);
                }
                if (!portalModuleBase.Page.ClientScript.IsStartupScriptRegistered("_SessionTimerVars_"))
                {
                    portalModuleBase.Page.ClientScript.RegisterStartupScript(portalModuleBase.Page.GetType(), "_SessionTimer_", timerVars, true);
                }
            }
        }

    }
}

For now, edit the Angular module settings entering a value of 18 for the session timer threshold. This will make it so the session dialog opens only after 2 minutes with a default session duration of 20 minutes. Typically, you will want to use a value of 2 for the threshold, but we don’t want to have to wait 18 minutes for the dialog to open while we test it out.

Add the SessionTimerJavaScript method call to your View module control OnLoad method as follows.

View.ascx.cs
protected override void OnLoad(EventArgs e)
{
    DotNetNuke.Framework.jQuery.RequestUIRegistration();

    //load AngularJS Session Timer
    SessionTimerJavaScript(this);
}

Build the module and refresh the page in DNN. When you view the page source in the browser, you should see the following near the bottom of the page if your session timer threshold is set to 18 minutes and session duration is the default of 20 minutes:

HTML view source

<script src="/DesktopModules/Angular/Scripts/SessionTimer.js" type="text/javascript">/script>
<script type="text/javascript">
//<![CDATA[
sessionApp.constant('settings',{ threshold: 1080000, sessionDuration: 1200000 });
//]]>
</script>

jQuery UI Dialog

Add this jQuery UI markup to the bottom of the default (View) module control.

View.ascx

<div ng-app="sessionApp" ng-controller="SessionCtrl">
    <div id="dialog-session" title="Session Expiration">
        <p>Your session will expire in:</p>
        <p>{{ remainingTime | minute }} minutes.</p>
        </div>
    </div>
</div>

Add this css to the module stylesheet so the dialog is hidden when the View module control loads.

module.css
#dialog-session {
    display: none;
}

Session Timer AngularJS Timeout

Below the sessionApp angular module variable that was added last time we touched this JavaScript file, add the variable for the jQuery UI dialog selector, sessionAppTimeoutId var, sessionApp filter for dialog minutes display and sessionApp controller code so you file looks like this:

SessionTimer.js
'use strict';

var sessionAlert = $("#dialog-session");

/* module def */
var sessionApp = angular.module('sessionApp', []);
var sessionAppTimeoutId;

/* filters */
sessionApp.filter('minute', function () {
    return function (milliseconds) {
        var seconds = Math.floor(milliseconds / 1000);
        var minutes = Math.floor(seconds / 60);
        seconds = seconds % 60;
        return minutes + ":" + (seconds < 10 ? "0" + seconds : seconds);
    }
});

/* controllers */
sessionApp.controller('SessionCtrl', ['$scope', '$timeout', '$window', 'settings', function ($scope, $timeout, $window, settings) {

    $scope.checkInterval = 1000;
    $scope.threshold = settings.threshold;
    $scope.duration = $scope.remainingTime = settings.sessionDuration;

    $scope.hide = function () {
        sessionAlert.dialog("close");
    }
    $scope.displayDialog = function () {
        
        //remove logging in production
        console.log('displayDialog');
       
        sessionAlert.dialog({
            dialogClass: "dnnFormPopup",
            modal: true,
            resizable: false,
            buttons: [{
                text: "Extend Session",
                click: function () {
                    $window.location.reload();
                }
            }]
        });
    }
    $scope.checkSession = function () {
        $timeout.cancel(sessionAppTimeoutId);

        $scope.remainingTime = $scope.remainingTime - $scope.checkInterval;

        //remove logging in production
        console.log($scope.remainingTime);

        if ($scope.remainingTime === settings.threshold) {
            $scope.displayDialog();
        }

        if ($scope.remainingTime > 0) {
            sessionAppTimeoutId = $timeout($scope.checkSession, $scope.checkInterval);
        } else {
            sessionAlert.dialog("option", "buttons", [{ text: "Ok", click: function () { $window.location.reload(); } }]);
        }
    }
    $scope.start = function () {
        sessionAppTimeoutId = $timeout($scope.checkSession, $scope.checkInterval);
    }

    $scope.start();

}]);

Forms Authentication (added July 10, 2013)

The default DNN installation uses Forms Authentication that will log out the user after 60 minutes of inactivity as shown in this web.cofig node:

web.config
<authentication mode="Forms">
    <forms name=".DOTNETNUKE" protection="All" timeout="60" cookieless="UseCookies" />
</authentication>

To make use of the forms timeout since that overrides the Session duration value we are currently using, in AngularModuleBase.cs:

Replace

int sessionDurationInMilliSeconds = portalModuleBase.Page.Session.Timeout * 60000;

With

int formsTimeoutDurationInMilliSeconds = System.Web.Security.FormsAuthentication.Timeout.Milliseconds;

Refactor the respective sessionDurationInMilliSeconds var names in the remainder of the SessionTimerJavaScript method with the new name: formsTimeoutDurationInMilliSeconds.

This project is available for browsing and download at GitHub:
https://github.com/jimfrenette/DnnAngular

To Do List…

  1. Check permissions and if the page is public, do not the start the Session Timer.
  2. Add validation to the Session Timer Threshold input so its value cannot be set higher then the session duration.

Resources

jQuery Mobile Cascaded Selects using MVC4 and KnockoutJs

This post documents building cascading select inputs using jQuery and Knockout for the user interface and MVC4 to serve the content.

Step 1

OPEN or CREATE the MvcMobileApp that includes the Chinook Models and Data Access class [chinook-asp-net-mvc-4-web-application]

View

CREATE a view that will be used to render the cascading selects to the browser. In the Solution Explorer, right click on the Views folder and create a new folder names Chinook. Then right click on the new Chinook folder and Add a new View named CascadeSelect.

Controller

CREATE a controller class for the cascading selects. In the Solution Explorer, right click on the Controllers folder and add a new empty MVC controller named ChinookController

Below the public ActionResult Index() method, add these methods as shown below:

  1. CascadeSelect() returns the CascadeSelect View
  2. GetArtists() returns an json array of artists
  3. GetAlbums() returns an json array of albums by artist id
  4. GetTracks() returns an json array of tracks by album id
MvcMobileApp/Controllers/ChinookController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMobileApp.Controllers
{
    public class ChinookController : Controller
    {
        //
        // GET: /Chinook/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult CascadeSelect()
        {
            return View();
        }

        public ActionResult GetArtists()
        {
            var artistList = Chinook.GetArtists();
            return this.Json(artistList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetAlbums(string id)
        {
            var albumList = Chinook.GetAlbums(Convert.ToInt32(id));
            return this.Json(albumList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetTracks(string id)
        {
            var trackList = Chinook.GetTracks(Convert.ToInt32(id));
            return this.Json(trackList, JsonRequestBehavior.AllowGet);
        }

    }
}

EDIT Views/Shared/_Layout.cshtml

  1. move the client script bundle loaders into the document head
  2. add a link to the knockoutjs script in the document head
  3. in the document body, look for the opening div data-role=”page”, and add id=”page” to it as follows:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title</title>
        <meta name="viewport" content="width=device-width" />
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        @Styles.Render("~/Content/mobileCss", "~/Content/css")
        @Scripts.Render("~/bundles/modernizr")
        @Scripts.Render("~/bundles/jquery", "~/bundles/jquerymobile")
        @RenderSection("scripts", required: false)
        <script src="~/Scripts/knockout-2.1.0.js" type="text/javascript"></script>
    </head>
    <body>
        <div data-role="page" data-theme="b" id="page">
            <div data-role="header">
                @if (IsSectionDefined("Header")) {
                    @RenderSection("Header")
                } else {
                    <h1>@ViewBag.Title</h1>
                    @Html.Partial("_LoginPartial")
                }
            </div>
            <div data-role="content">
                @RenderBody()
            </div>
        </div>
    </body>
</html>

EDIT MvcMobileApp/Views/Chinook/CascadeSelect.cshtml, add this javascript to the bottom of the view.


<script type="text/javascript">
    //for jQueryMobile use this instead of document ready
    $("#page").live("pageinit", function (event) {

        getArtists();

    });

    var viewModel = function () {
        var self = this;
        self.artists = ko.observableArray([]);
        self.albums = ko.observableArray([]);
        self.tracks = ko.observableArray([]);

        //selected value observable and onchange subscription to fire data lookup for child select
        self.selectedAlbum = ko.observable();
        self.selectedAlbum.subscribe(function (value) {
            if (value) {
                getTracks(value);
            }
        });
        self.selectedArtist = ko.observable();
        self.selectedArtist.subscribe(function (value) {
            if (value) {
                getAlbums(value);
            }
        });
    };
    var viewModel = new viewModel();
    ko.applyBindings(viewModel);

    function getArtists() {
        $.getJSON("/Chinook/GetArtist", null, function (data) {
            viewModel.artists(data);
            // reset jQueryMobile selection label
            $("#select-artist").selectmenu("refresh");
        });
    }

    function getAlbums(artistId) {
        $.getJSON("/Chinook/GetAlbums/" + artistId, null, function (data) {
            viewModel.albums(data);
            // reset jQueryMobile selection label
            $("#select-album").selectmenu("refresh");
        });
    }

    function getTracks(albumId) {
        $.getJSON("/Chinook/GetTracks/" + albumId, null, function (data) {
            viewModel.tracks(data);
            // reset jQueryMobile selection label
            $("#select-track").selectmenu("refresh");
        });
    }
</script>

Insert this data bound markup above the javascript that was just added to CascadeSelect.cshtml

<label for="select-artist" class="ui-hidden-accessible">Artist</label>
<select name="select-artist" id="select-artist"
    data-bind="options: artists,
    optionsText: 'Name',
    optionsValue: 'ArtistId',
    optionsCaption: 'Select Artist',
    value: selectedArtist">
</select>

<div data-bind="visible: albums().length > 0">
    <label for="select-album" class="ui-hidden-accessible">Album</label>
    <select name="select-album" id="select-album"
        data-bind="options: albums,
        optionsText: 'Title',
        optionsValue: 'AlbumId',
        optionsCaption: 'Select Album',
        value: selectedAlbum">
    </select>
</div>

<div data-bind="visible: tracks().length > 0">
    <label for="select-track" class="ui-hidden-accessible">Track</label>
    <select name="select-track" id="select-track"
        data-bind="options: tracks,
        optionsText: 'Name',
        optionsValue: 'TrackId',
        optionsCaption: 'Select Track'">
    </select>
</div>

EDIT Views/Home/Index.cshtml and add this razor html helper code to the bottom that will render a link to the CascadeSelect view from the home view.

@Html.ActionLink("CascadeSelect", "CascadeSelect", "Chinook")

Run

Select F5 or one of the debug options to run the app.

Optimize

It is likely that on page load the first select does not populate before it can be accessed by the user.
Therefore, we will use the server side view model data source for the select list when the view is returned to the page on load.

EDIT Controllers/ChinookController.cs

CHANGE

public ActionResult CascadeSelect()
{
    return View();
}

TO

public ActionResult CascadeSelect()
{
    //return model for first select
    var artistList = Chinook.GetArtists();
    return View(artistList);
}

EDIT Views/Chinook/CascadeSelect.cshtml

On the very first line, add

@model List

NEXT, REPLACE

<select name="select-artist" id="select-artist"
    data-bind="options: artists,
    optionsText: 'Name',
    optionsValue: 'ArtistId',
    optionsCaption: 'Select Artist',
    value: selectedArtist">
</select>

WITH

<select name="select-artist" id="select-artist"
    data-bind="value: selectedArtist">
        <option value="">Select Artist</option>
@{
    if (Model != null)
    {
        foreach (var item in Model)
        {
            <option value="@item.ArtistId">@item.Name</option>
        }
    }
}
</select>

NEXT, change the visible binding on the select-album parent div.
REPLACE

<div data-bind="visible: albums().length > 0">

WITH

<div data-bind="visible: selectedArtist">

NEXT, change the visible binding on the select-track parent div.
REPLACE

<div data-bind="visible: tracks().length > 0">

WITH

<div data-bind="visible: selectedArtist, visible: selectedAlbum">

NOTE: Bindings are applied in order from left to right. In the data-bind above, visible: selectedArtist is applied before visible: selectedAlbum.

In the javascript code block at the bottom of Views/Chinook/CascadeSelect.cshtml, DELETE these two lines of javascript:

getArtists();

self.artists = ko.observableArray([]);

AND

DELETE the function:

function getArtists() {
    $.getJSON("/Chinook/GetArtists", null, function (data) {
        viewModel.artists(data);
        // reset jQueryMobile selection label
        $("#select-artist").selectmenu("refresh");
    });
}

Review

After all of those changes, your Views/Chinook/CascadeSelect.cshtml view should like this:

@model List
@{
    ViewBag.Title = "CascadeSelect";
}

<h2>CascadeSelect</h2>

<label for="select-artist" class="ui-hidden-accessible">Artist</label>
<select name="select-artist" id="select1"
    data-bind="value: selectedArtist">
        <option value="">Select Artist</option>
@{
    if (Model != null)
    {
        foreach (var item in Model)
        {<a href="~/Scripts/">~/Scripts/</a>
            <option value="@item.ArtistId">@item.Name</option>
        }
    }
}
</select>

<div data-bind="visible: selectedArtist">
    <label for="select-album" class="ui-hidden-accessible">Album</label>
    <select name="select-album" id="select2"
        data-bind="options: albums,
        optionsText: 'Title',
        optionsValue: 'AlbumId',
        optionsCaption: 'Select Album',
        value: selectedAlbum">
    </select>
</div>

<div data-bind="visible: selectedArtist, visible: selectedAlbum">
    <label for="select-track" class="ui-hidden-accessible">Track</label>
    <select name="select-track" id="select3"
        data-bind="options: tracks,
        optionsText: 'Name',
        optionsValue: 'TrackId',
        optionsCaption: 'Select Track'">
    </select>
</div>

<script type="text/javascript">
    //for jQueryMobile use this instead of document ready
    $("#page").live("pageinit", function (event) {
        //getArtists();
    });

    var viewModel = function () {
        var self = this;
        self.albums = ko.observableArray([]);
        self.tracks = ko.observableArray([]);

        //selected value observable and onchange subscription to fire data lookup for child select
        self.selectedAlbum = ko.observable();
        self.selectedAlbum.subscribe(function (value) {
            if (value) {
                getTracks(value);
            }
        });
        self.selectedArtist = ko.observable();
        self.selectedArtist.subscribe(function (value) {
            if (value) {
                getAlbums(value);
            }
        });
    };
    var viewModel = new viewModel();
    ko.applyBindings(viewModel);

    function getAlbums(artistId) {
        $.getJSON("/Chinook/GetAlbums/" + artistId, null, function (data) {
            viewModel.albums(data);
            // reset jQueryMobile selection label
            $("#select-album").selectmenu("refresh");
        });
    }

    function getTracks(albumId) {
        $.getJSON("/Chinook/GetTracks/" + albumId, null, function (data) {
            viewModel.tracks(data);
            // reset jQueryMobile selection label
            $("#select-track").selectmenu("refresh");
        });
    }
</script>

Run

Select F5 or one of the debug options to run the app.

    References:

  • KnockoutJS Simplify dynamic JavaScript UIs by applying the Model-Vifew-View Model (MVVM) pattern.

Chinook ASP.NET MVC 4 Web Application

For this tutorial, you will need:

In Visual Studio, create a new C# ASP.NET MVC 4 Web Application:
Visual Studio 2012 New Project Dialog

Next, select the Mobile Application Project Template. This template includes jQuery, jQuery intellisense for Visual Studio, jQuery Mobile and Knockoutjs javascript files.
Visual Studio 2012 Project Template Dialog

If you haven’t done so yet, now would be a good time to install the Chinook database to your SQL Server.

Make sure you can Access your SQL Server [Configure Windows Firewall]

Open up the Web.config and replace

<connectionStrings>
 <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MvcMobileApp-20120922134208;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MvcMobileApp-20120922134208.mdf" />
</connectionStrings>

With *

<connectionStrings>
 <add name="ChinookConnection" providerName="System.Data.SqlClient" connectionString="Data Source=VGN-FW290\SQLEXPRESS;Initial Catalog=Chinook;Integrated Security=True" />
</connectionStrings>

* VGN-FW290 is the name of my computer. Replace this with your computer name

SQL Stored Procedures

Open up SQL Server Management Studio and connect to the server that is defined in your web.config connection string. In the Object Explorer(F8) Right click on the Stored Procedures node of the expanded Databases.Chinook tree and Select “New Stored Procedure”.

The first stored procedure you will create, GetArtists uses CTE (Common Table Expression) to handle the paging of the records. Especially in web applications, it is a best practice to utilize paging when returning records from large tables.

The next stored procedure you will create, GetAlbums is for selecting Album(s).

The third stored procedure you will create, GetTracks is for selecting Track(s).

SQL Function

New Scalar-value function

Create this user defined function to add a computed AlbumCount column to the Artist Table.

Now we need to bind the SQL Function to the Artist table. In the Object Explorer(F8), expand the Tables node and Artist table node. Right click on the Columns folder and select New Column from the context menu. Enter AlbumCount under Column Name and int under Data Type. Navigate to the Column Properties tab, expand the Computed Column Specification and enter “([dbo].[CountAlbums]([ArtistId]))” for the (Formula) value. Save the Artist table (Ctrl+S).

Data Models

Create a data model class for the Chinook data objects. In the Solution Explorer, right click on the Models folder and add a new file named ChinookModels.cs

MvcMobileApp/Models/ChinookModels.cs

using System;

namespace MvcMobileApp.Models
{
    public class AlbumModel
    {
        public int AlbumId { get; set; }
        public string Title { get; set; }
        public int ArtistId { get; set; }
    }

    public class ArtistModel
    {
        public int ArtistId { get; set; }
        public string Name { get; set; }
        public int AlbumCount { get; set; }
    }

    public class TrackModel
    {
        public int TrackId { get; set; }
        public string Name { get; set; }
        public int AlbumId { get; set; }
        public int MediaTypeId { get; set; }
        public int GenreId { get; set; }
        public string Composer { get; set; }
        public int Milliseconds { get; set; }
        public int Bytes { get; set; }
        public double UnitPrice { get; set; }
    }
}

Data Access

Create a data access class that can be used by any controller in our application to interface with the Chinook database. In the Solution Explorer, right click on the MvcMobileApp project and add a new class called Chinook. The result is a file named Chinook.cs in the root of our application.

MvcMobileApp/Chinook.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

namespace MvcMobileApp
{
    using Models;

    public class Chinook
    {
        public static List GetAlbums(int artistId)
        {
            List albumList = new List();
            SqlConnection conn = new SqlConnection(Config.ChinookConnection);
            try
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("GetAlbums", conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@ArtistId", artistId);
                SqlDataReader dr = cmd.ExecuteReader();
                while (dr.Read())
                {
                    albumList.Add(new AlbumModel()
                    {
                        AlbumId = Convert.ToInt32(dr["AlbumId"]),
                        Title = dr["Title"].ToString(),
                        ArtistId = Convert.ToInt32(dr["ArtistId"]),
                    });
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
            return albumList;
        }

        public static List GetArtists()
        {
            //get all artists
            List artistList = GetArtists(0,1);
            return artistList;
        }

        public static List GetArtists(int pageSize, int pageNumber)
        {
            List artistList = new List();
            SqlConnection conn = new SqlConnection(Config.ChinookConnection);
            try
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("GetArtists", conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@PageSize", pageSize);
                cmd.Parameters.AddWithValue("@PageNumber", pageNumber);
                SqlDataReader dr = cmd.ExecuteReader();
                while (dr.Read())
                {
                    artistList.Add(new ArtistModel()
                    {
                        ArtistId = Convert.ToInt32(dr["ArtistId"]),
                        Name = dr["Name"].ToString(),
                        AlbumCount = Convert.ToInt32(dr["AlbumCount"])
                    });
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
            return artistList;
        }

        public static List GetTracks(int albumId)
        {
            List trackList = new List();
            SqlConnection conn = new SqlConnection(Config.ChinookConnection);
            try
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("GetTracks", conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@AlbumId", albumId);
                SqlDataReader dr = cmd.ExecuteReader();
                while (dr.Read())
                {
                    trackList.Add(new TrackModel()
                    {
                        TrackId = Convert.ToInt32(dr["TrackId"]),
                        Name = dr["Name"].ToString(),
                        AlbumId = Convert.ToInt32(dr["AlbumId"]),
                        MediaTypeId = Convert.ToInt32(dr["MediaTypeId"]),
                        GenreId = Convert.ToInt32(dr["GenreId"]),
                        Composer = dr["Composer"].ToString(),
                        Milliseconds = Convert.ToInt32(dr["Milliseconds"]),
                        Bytes = Convert.ToInt32(dr["Bytes"]),
                        UnitPrice = Convert.ToDouble(dr["UnitPrice"]),
                    });
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
            return trackList;
        }

        private class Config
        {
            static public String ChinookConnection { get { return WebConfigurationManager.ConnectionStrings["ChinookConnection"].ConnectionString; } }
        }

    }

}

Controller

CREATE a controller class for the cascading selects. In the Solution Explorer, right click on the Controllers folder and add a new empty MVC controller named ChinookController

Below the public ActionResult Index() method, add these methods as shown below:

  1. GetArtists() returns an json array of artists
  2. GetAlbums() returns an json array of albums by artist id
  3. GetTracks() returns an json array of tracks by album id

MvcMobileApp/Controllers/ChinookController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMobileApp.Controllers
{
    public class ChinookController : Controller
    {
        //
        // GET: /Chinook/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GetArtists()
        {
            var artistList = Chinook.GetArtists();
            return this.Json(artistList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetAlbums(string id)
        {
            var albumList = Chinook.GetAlbums(Convert.ToInt32(id));
            return this.Json(albumList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetTracks(string id)
        {
            var trackList = Chinook.GetTracks(Convert.ToInt32(id));
            return this.Json(trackList, JsonRequestBehavior.AllowGet);
        }

    }
}

DotNetNuke Modal PopUps

Task 1, create a jQuery UI dialog to confirm a postback action invoked by a ASP.NET button control.

In your module’s ascx file, add a button control and a div to contain you dialog.

<asp:Button ID="btnDelete" runat="server"
	AutoPostBack="false"
	ButtonType="LinkButton"
	CssClass="main_button"
	OnClick="btnDelete_Click"
	Text="Delete"
	UseSubmitBehavior="false">
</asp:Button>

<div id="dlgDeleteConfirm" title="Confirm">
	<div class="confirmDialog">Are you sure you want to delete this?</div>
</div>

In you ascx code behind, create a method to create a client script block, you call can call on page load. This modular approach of passing server vars to the client is one I like the best. With this method you can modify the code to pass in arguments and also include them when writing your script block, etc..

private void Page_Load(object sender, System.EventArgs e)
{
	ClientScriptBlock();
}

private void ClientScriptBlock()
{
	Type csType = this.GetType();
	ClientScriptManager cs = Page.ClientScript;
	if (!cs.IsStartupScriptRegistered(csType, "Vars"))
	{
		StringBuilder sb = new StringBuilder();
		sb.Append("");
		cs.RegisterClientScriptBlock(csType, "Vars", sb.ToString());
	}
}

protected void btnDelete_Click(object sender, EventArgs e)
{
 //delete method followed by redirect or whatever on success
}
.confirmDialog {
	padding: 15px 15px 0 15px;
	font-weight: bold;
}

jQuery – I had trouble getting .dnnConfirm to handle the postback with it’s isButton option, so I used a standard jQuery UI Dialog instead.

jQuery(document).ready(function ($) {
	$("#dlgDeleteConfirm").dialog({
		autoOpen: false,
		modal: true,
		width: 300,
		height: 140,
		resizable: false,
		dialogClass: "dnnFormPopup",
		buttons: {
			"Yes": function() {<%=this.Page.ClientScript.GetPostBackEventReference(new PostBackOptions(this.btnDelete))%>;},
			"No": function() {$(this).dialog('close');}
		}
	});
	$("#" + btnDelete_ClientID).click(function () {
		$("#dlgDeleteConfirm").dialog('open'); return false;
	});
});

NOTE: You need to make sure that DNN loads the jQuery UI in either OnInit or OnLoad in your Module Controls code behind.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    //register jQuery-UI - no need to register jQuery
    //since jQuery-UI registration method will handle this 
    DotNetNuke.Framework.jQuery.RequestUIRegistration();
}

Task 2, create a jQuery UI dialog to contain an Edit form

Resources

Random full page background

This Tutorial is written with Drupal 7 in mind. However, it can be applied to any website.

While upgrading and redesigning thebga.org golf association website, I decided to figure out how to have a random full page background part of the design. Drupal 7 makes it easy to include js and keep it, the theme and templates all separate from the Drupal core. The key is separation of your theme and it’s modules from the Drupal core and it’s modules. The Zen theme and Starter Kit make this very easy to accomplish.

Create or add this code to a javascript file in your themes “js” folder. I saved the file as random-bg.js (/sites/all/themes/thebga/js/random-bg.js).

(function ($) {
    $(document).ready(function() {
	var bgimages = [
	    'ovl_2010_wellman_rw.jpg',
	    'ovl_2010_wellman_sunday_am.jpg',
	    'ovl_2010_wellman_tobaccoroad_01.jpg',
	    'ovl_2010_wellman_tobaccoroad_14.jpg',
	    'ovl_2011_bridges_15.jpg',
	    'ovl_2011_dos-equis_sunday.jpg',
	    'ovl_2011_spam_friday01.jpg',
	    'ovl_2011_spam_friday02.jpg',
	    'ovl_2011_spam_friday03.jpg'
	];
	$('body').css({'background-image': 'url(sites/all/themes/thebga/images/bg-body/' + bgimages[Math.floor(Math.random() * bgimages.length)] + ')'});
    });
})(jQuery);

Note the JavaScript closure around

$(document).ready(function()

more info on that here: drupal.org/node/171213.

Put your background images in /sites/all/themes/thebga/images/bg-body/. The bg-body directory is optional, I created it to keep the background images separate from the other images used in the theme.

Add this code to the page-backgrounds.css (/sites/all/themes/thebga/css/page-backgrounds.css).

body {
    /* background color + default image (in-case random-bg.js fails) */
    background: #003c00 url('../images/bg-body/ovl_2011_dos-equis_sunday.jpg') no-repeat center center fixed;
    -webkit-background-size: cover;
    -moz-background-size: cover;
    -o-background-size: cover;
    background-size: cover;
}

Open the theme .info file and after

; Optionally add some JavaScripts to your theme.

add this line to load your javascript file:

scripts[] = js/random-bg.js

That is it! Every time the page is refreshed, a random background is loaded using a jQuery css modifier.

Using jQuery in WordPress Posts

This post by Chris Coyier shows how to use jQuery inside a WordPress post. This helped me work around an Apache web server security configuration issue that was not allowing me to post SQL code snippets inside of my posts.

403 response when I try to post certain SQL code snippets:


Forbidden

You don’t have permission to access /wp-admin/post.php on this server.


First, follow the instructions in Chris’s post which basically has you modify the your themes header.php to load jQuery that is included with WordPress just before the wp_head() function call as follows.

Replace
<?php wp_head(); ?>;
With
<?php wp_enqueue_script("jquery");
wp_head(); ?>;

Next, create a htm file to hold SQL code snippets. The pre element id attribute allows for jQuery loading of individual fragments instead of the entire .htm file. Additional SQL code snippets can be added to this file each separated by a pre element with a unique id attribute:

<html>
	<body>
		<pre id="sp_Procedure1">
		<!-- insert SQL Code here -->
		</pre>
		<pre id="sp_Procedure2">
		<!-- insert SQL Code here -->
		</pre>
	</body>
</html>

Add a placeholder div to your post for the jQuery load target:

<div id="sql"></div>

Now add this JavaScript to the bottom of your post to load the .htm fragment*.

<script type="text/javascript">
var $j = jQuery.noConflict();
$j(function(){
	$j('#sql').load('http://jimfrenette.com/wp-content/includes/sql.htm #sp_Procedure2');
});
</script>

*Change the path as needed. More info: jQuery .load().

jQuery – MSSQL Pagination and Data Templates

Here is a tutorial to show you how to paginate JSON data in the client using jQuery, jQuery Templates plugin, ASP.NET and SQL Server.

For this tutorial, you will need:

From the Chinook archive, copy
Chinook_SqlServer.sql
Chinook_SqlServer_AutoIncrementPKs.sql
CreateSqlServer.bat

to your MSSQL Data folder.

i.e., C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data

Run the CreateSqlServer.bat

Open up Visual Studio | Server Explorer (Ctrl+Alt+S)

Right Click on “Data Connections” and select “Add Connection…”

For the Data source, select “Microsoft SQL Server”

Select the SQL Server Name. i.e., [COMPUTER NAME]\SQLEXPRESS

Connect to a database, Select the Chinook database

Create the sp_ArtistsByPage.sql stored procedure:

In Visual Studio, Create a new project (Ctrl+Shift+N)

Select ASP.NET Empty Web Application (This template is available when .NET Framework4 is selected)

Add a database Connection String in the web.config similar to this one. Note: VGN-FW290 is the name of my computer. Replace this with yours obviously. Tip: in Server Explorer, Expand the Data Connections node and right-click on the Chinook connection. Select properties(F4) and copy the Connection String.


<connectionStrings>
    <add name="ChinookConnection" connectionString="Data Source=VGN-FW290\SQLEXPRESS;Initial Catalog=Chinook;Integrated Security=True" providerName="System.Data.SqlClient"/>
</connectionStrings>

Copy the jQuery .js file to the Scripts folder of the application. Then right click on the Scripts folder and select add existing item (Alt+Shift+A). Browse to the folder you just copied jQuery to and select it so it is part of the project.

Repeat the process for the jQuery Pager and Templates plugins, (jquery.pager.js and jquery.tmpl.min.js). Use either the uncompressed or minified (*.min.js) versions.

Drag the jQuery library Template plugin files from the Solution Explorer into the Default.aspx source to the line just before the closing </form> tag.

Add the following css to \Styles\screen.css, add it to the project and drag into the source before the closing </head> tag.

body
{
    margin:5px;
    background:#FFF;
    font-family: Arial, Sans-Serif;
    font-size:90%;
}
a {color:#369;}
a.active
{
    color:#000;
    text-decoration:none;
}
a:hover {
    color:#FFF;
    background:#369;
    text-decoration:none;
}
a.disabled {color:#ADADAD;}
a.disabled:hover {
    color:#ADADAD;
    background:none;
}
h1, h2, h3 {
    margin:.8em 0 .2em 0;
    padding:0;
}
p {
    margin:.4em 0 .8em 0;
    padding:0;
}
img {
    margin:0;
    border:0;
}
#artists {position: relative; width:700px;}
#artistTable {border-collapse:collapse; margin-top:10px; clear:both; }
#artistList tr.highlight {background-color: #FDF5CE;}
#artistList td {padding: 2px 5px 2px 5px;}
#loading {
    width: 24px;
    height:24px;
    background-image: url('../Styles/ajax-loader.gif');
}
#pager ul.pages
{
    display: block;
    border: none;
    text-transform: uppercase;
    font-size: 10px;
    padding: 0;
}
#pager ul.pages li
{
    list-style: none;
    float: left;
    border: 1px solid #888;
    text-decoration: none;
    margin: 0 5px 0 0;
    padding: 5px;
    background-color: #FFF;
}
#pager ul.pages li:hover
{
    border: 1px solid #00376F;
}
#pager ul.pages li.pgEmpty
{
    border: 1px solid #CCC;
    color: #AAA;
    cursor: default;
}
#pager ul.pages li.pgCurrent
{
    border: 1px solid #003F7E;
    color: #042CCA;
    font-weight: 700;
    background-color: #dadada;
}
#pageSize
{
    position: absolute;
    top: -10px;
    right: 0;
    width:80px;
    text-align:right;
}
#pageSize label
{
    font-size:10px;
    padding: 0 2px 0 0;
}
#pageSize select {width:50px;}

Default.aspx should now look something like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="PaginationSQL._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Styles/screen.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div> </div>
    <script src="Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.pager.js" type="text/javascript"></script>
    <script src="Scripts/jquery.tmpl.js" type="text/javascript"></script>
    </form>
</body>
</html>

Solution Explorer

Add a Generic Handler to the project and name it Json.ashx. Go to www.preloaders.net and download a preloader animation. Copy it to the Styles folder and add it to the project. Your Solution Explorer tree should look something like this one shown at left.

C# code for the handler

using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Web;
using System.Web.Configuration;
using System.Web.Script.Serialization;

namespace PaginationSQL
{
    /// 
    /// Summary description for Json
    /// 
    public class Json : IHttpHandler
    {
        private string ChinookConnection = WebConfigurationManager.ConnectionStrings["ChinookConnection"].ConnectionString;
        private string data = "";
        private int page_number = 1;
        private int page_size = 25;
        private int total_count = 0;

        public void ProcessRequest(HttpContext context)
        {
            page_size = Convert.ToInt32(context.Request.QueryString["page_size"]);
            page_number = Convert.ToInt32(context.Request.QueryString["page_number"]);

            StringBuilder json = new StringBuilder();

            string artistId = "";
            string name = "";

            DataSet artist = GetArtistsByPage(page_size, page_number);
            foreach (DataRow dr in artist.Tables[0].Rows)
            {
                artistId = dr["ArtistId"].ToString();
                name = JavaScriptStringEncode(dr["Name"].ToString());

                json.Append("{");
                json.Append("\"artistId\":\"" + artistId + "\",");
                json.Append("\"artistName\":\"" + name + "\"");
                json.Append("},");

                total_count = Convert.ToInt32(dr["TotalCount"]);
            }
            data = "{\"items\":[";
            if (json.Length != 0)
            {
                //remove trailing comma since we can't determine if last record during the append
                //data += "[" + json.ToString(0, json.Length - 1) + "]";
                data += json.ToString(0, json.Length - 1);
            }
            data += "],\"page_number\":" + page_number + ",\"page_size\":" + page_size + ",\"total_count\":" + total_count + "}";

            context.Response.Clear();
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.ContentType = "application/json;charset=utf-8";
            context.Response.Flush();
            context.Response.Write(data);
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        //if your app has more data access methods, move this into a
        //data access layer class, such as Dal.cs
        public DataSet GetArtistsByPage(int pageSize, int pageNumber)
        {
            SqlConnection con = new SqlConnection(ChinookConnection);
            SqlCommand cmd = new SqlCommand("ch_ArtistsByPage", con);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@pageSize", pageSize);
            cmd.Parameters.AddWithValue("@pageNumber", pageNumber);
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            DataSet ds = new DataSet();
            da.Fill(ds, "Artists");
            return ds;
        }

        //if your app works with json elsewhere, move this into
        //another class - perhaps Utils.cs
        public string JavaScriptStringEncode(string message)
        {
            if (String.IsNullOrEmpty(message))
            {
                return message;
            }
            StringBuilder builder = new StringBuilder();
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            serializer.Serialize(message, builder);
            return builder.ToString(1, builder.Length - 2); // remove first + last quote
        }

    }
}

Finally, add this template and jQuery page specific javascript at the bottom of the page above the closing </form> tag. Press F5 to test the web application.


<script id="artistTmpl" type="text/x-jquery-tmpl">
    <tr>
        <td align="right" valign="top">${artistId}</td>
        <td>${artistName}</td>
    </tr>
</script>
<script type="text/javascript">
    $(document).ready(function () {

        $("#loading").hide();

        var pageIndex = 1, pageSize = 10, pageCount = 0;
        getArtists(pageIndex);
        $("#pageSize select").change(function () {
            pageIndex = 1
            pageSize = $(this).val();
            getArtists(pageIndex);
        });

        function getArtists(index) {
            var query = "Json.ashx?page_number=" + index + "&page_size=" + pageSize;
            pageIndex = index;
            $("#artistList")
            .fadeOut("medium", function () {
                $("#loading").show()
                $.ajax({
                    dataType: "json",
                    url: query,
                    jsonp: "$callback",
                    success: showArtists
                });
            });
        }

        function showArtists(data) {
            pageCount = Math.ceil(data.total_count / pageSize),
            artists = data.items;
            $("#pager").pager({ pagenumber: pageIndex, pagecount: pageCount, buttonClickCallback: getArtists });
            $("#artistList").empty()
            $("#artistTmpl").tmpl(artists).appendTo("#artistList")
            $("#loading").hide().find("div").fadeIn(4000).end()
            $("#artistList").fadeIn("medium")
            $("#artistList tr").hover(
                function () {
                    $(this).addClass("highlight");
                },
                function () {
                    $(this).removeClass("highlight");
                });
        }

    });
</script>

This project is available for browsing and download at GitHub:

Source Code