Tutorial - Building NivoSlider Extension (Part 3)

16. September 2011 13:26 by rtur.net in Extensions, Tutorials  //  Tags: ,   //   Comments (11)

Data Persistence

db_1What we need next is to save metadata for each picture used by every slider, and also we need to be able to add and delete all these records. Extension settings are standard way of doing it in BlogEngine - you declare what kind of data you want to maintain, set initial values and first time extension runs it will instantiate settings object and save it on the back-end. To maintain these data, blogger goes to admin/extensions and clicks extension link in the right sidebar. This will load auto-generated form where settings can be edited. The code below would be sufficient:

namespace rtur.net.NivoSlider
{
    using BlogEngine.Core.Web.Extensions;

    public class NivoSettings
    {
        static ExtensionSettings settings;

        public static ExtensionSettings ImageData
        {
            get
            {
                if (settings == null)
                {
                    var extensionSettings = new ExtensionSettings(Constants.ExtensionName);
                    
                    extensionSettings.AddParameter(Constants.UID, "UID");
                    extensionSettings.AddParameter(Constants.Url, "Url");
                    extensionSettings.AddParameter(Constants.Title, "Title");

                    extensionSettings.AddValues(new[] { "slider1:sample1.png", "#", "" });
                    extensionSettings.AddValues(new[] { "slider1:sample2.png", "#", "sample two" });

                    settings = ExtensionManager.InitSettings(Constants.ExtensionName, extensionSettings);
                }
                return settings;
            }
        }
    }
}

But in our case, would be helpful also allow to upload images. For this, we'll go with custom admin page. Custom page is an external page provided by extension author, by convention we put it under user controls: ~/User controls/NivoSlider/Admin.aspx. To let extension manager know what page to load instead of auto-generated form, new admin page has to be registered:

get
{
	if (settings == null)
	{
		...

		ExtensionManager.SetAdminPage(Constants.ExtensionName, Constants.AdminUrl);
	}
	return settings;
}

Now that we have images handled by settings dynamically, repository code has to be adjusted to use settings instead of hard-coded values. One little perk we can provide is a link from slider straight to admin screen – if user is administrator, this link will be added in the lower-right corner of the slider.

namespace rtur.net.NivoSlider
{
    using System.Data;
    using BlogEngine.Core;

    public class Repository
    {
        public static string GetSlider(string id, string width = "960", string height = "370")
        {
            if (NivoSettings.ImageData == null || NivoSettings.ImageData.GetDataTable().Rows.Count < 1)
                return "";

            var div = string.Format("<div id=\"{0}\" style=\"width: {1}px; height: {2}px;\">", id, width, height);

            const string img = "<a href=\"{0}\"><img runat=\"server\" src=\"~/image.axd?picture=Slider/{1}\" width=\"{2}\" height=\"{3}\" title=\"{4}\" alt=\"\" /></a>";

            foreach (DataRow row in NivoSettings.ImageData.GetDataTable().Rows)
            {
                string[] uid = row[Constants.UID].ToString().Split(':');
                
                if (uid.GetUpperBound(0) < 1)
                    continue;

                if(uid[0] == id)
                    div += string.Format(img, row[Constants.Url], uid[1], width, height, row[Constants.Title]);
            }

            div += "</div>";

            if (Security.IsAdministrator)
            {
                var left = int.Parse(width) - 40;
                var url = Blog.CurrentInstance.RelativeWebRoot + Constants.AdminUrl.Replace("~/", "") + "?ctrl=" + id;
                div += string.Format("<a href=\"{0}\" runat=\"server\" class=\"nivo-edit\" style=\"left:{1}px\">Edit</a>", url, left);
            }
            
            div += "<script type=\"text/javascript\">$(window).load(function (){ LoadSlider('#" + id + "');});</script>";

            return div;
        }
    }
}

Because strings are evil, tiny helper class will turn them all into constants. This way we can avoid case-sensitivity issues, mistyping and other fun to debug things.

namespace rtur.net.NivoSlider
{
    public class Constants
    {
        public const string ExtensionName = "NivoSlider";
        public const string AdminUrl = "~/User controls/NivoSlider/Admin.aspx";
        public const string ImageFolder = "/files/Slider";
        public const string UID = "UID";
        public const string Url = "Url";
        public const string Title = "Title";
    }
}

Custom Administration

Now we can take over settings maintenance by providing our own implementation. We need to create form to add/delete records and upload image file. Because page inherits from admin.master it will have same look and feel as rest of the admin pages. ASP.NET grids are generally heavy on resources, so we hand-code images table. Here is what resulting Admin.aspx looks like:

<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/admin/admin.master" CodeFile="Admin.aspx.cs" Inherits="rtur.net.NivoSlider.SliderAdmin" %>
<%@ MasterType VirtualPath="~/admin/admin.master" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="rtur.net.NivoSlider" %>
<asp:Content ID="Content1" ContentPlaceHolderID="cphAdmin" Runat="Server">
<div class="content-box-outer">
    <div class="content-box-full">
        <div>
            <h1>Settings: Slider</h1>
            <div style="float: left; width: 500px">
                <p>
                    <span style="width: 120px; display:inline-block;">Control ID:</span>
                    <asp:TextBox ID="txtConrolId" runat="server" MaxLength="100"></asp:TextBox>
                    <asp:RequiredFieldValidator runat="server" ID="reqCtrlId" ValidationGroup="data" ControlToValidate="txtConrolId" Text="<%$ Resources:labels, required %>" />
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;">Site URL:</span>
                    <asp:TextBox ID="txtUrl" Width="300" runat="server" MaxLength="250"></asp:TextBox>
                    <asp:RequiredFieldValidator runat="server" ID="reqSiteUrl" ValidationGroup="data" ControlToValidate="txtUrl" Text="<%$ Resources:labels, required %>" />
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;"><%=Resources.labels.title %>:</span>
                    <asp:TextBox ID="txtTitle" Width="300" runat="server" MaxLength="250"></asp:TextBox>
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;"><%=Resources.labels.upload %>:</span>
                    <asp:FileUpload runat="server" id="txtUploadImage" />
                    <asp:RequiredFieldValidator runat="server" ID="reqUpload" ValidationGroup="data" ControlToValidate="txtUploadImage" Text="<%$ Resources:labels, required %>" />
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;"> </span>
                    <asp:button runat="server" ValidationGroup="data" CssClass="btn primary" id="btnSave" Text=" Add " OnClick="SaveItem" />    
                </p>
            </div>

            <div class="clear"></div>
            
            <div>
                <table id="tblImgages" class="beTable rounded">
                  <thead>
                    <tr>
                      <th width="25"> </th>
                      <th width="120">Conrol Id</th>
                      <th width="200">Image File</th>
	                  <th width="250">Title</th>
                      <th width="auto">URL</th>
                      <th width="120"> </th>
                    </tr>
                  </thead>
                  <tbody>
                    <%
                       var i = 0;
                       foreach (DataRow row in NivoSettings.ImageData.GetDataTable().Rows)
                       {
                           var cls = i%2 == 0 ? "" : "alt";
                           string[] uid = row[Constants.UID].ToString().Split(':');
                           if (uid.GetUpperBound(0) < 1) continue;
                           var ctrl = uid[0];
                           var src = uid[1];
                    %>
                    <tr class="<%=cls %>">
                      <td><%=i + 1 %></td>
                      <td><%= ctrl %></td>
                      <td><%= src %></td>
	                  <td><%= row[Constants.Title] %></td>
                      <td><%= row[Constants.Url] %></td>
                      <td align="center" style="vertical-align:middle"><a class="deleteAction" href="?id=<%=row[Constants.UID].ToString() %>">Delete</a></td>
                    </tr>
                    <% i++; } %>
                  </tbody>
                </table>
            </div>
        </div>
    </div>
</div>
</asp:Content>

The code behind has "Save" and “Delete” methods with added “Upload” functionality, this will let blogger not only maintain image metadata but also conveniently upload image itself along with it.

using System;
using System.IO;
using BlogEngine.Core;
using BlogEngine.Core.Web.Extensions;

namespace rtur.net.NivoSlider
{
    public partial class SliderAdmin : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            App_Code.WebUtils.CheckIfPrimaryBlog(false);

            if (!Page.IsPostBack)
            {
                if (Request.QueryString["id"] != null)
                    DeleteItem(Request.QueryString["id"]);

                if (Request.QueryString["ctrl"] != null)
                    txtConrolId.Text = Request.QueryString["ctrl"];
            }
        }

        protected void SaveItem(object sender, EventArgs e)
        {
            try
            {
                Upload();

                var src = txtUploadImage.FileName.ToLowerInvariant();

                NivoSettings.ImageData.AddValues(new[] { txtConrolId.Text + ":" + src, txtUrl.Text, txtTitle.Text });

                ExtensionManager.SaveSettings(Constants.ExtensionName, NivoSettings.ImageData);
            }
            catch (Exception ex)
            {
                Utils.Log("rtur.net.SliderAdmin.SaveItem", ex);
            }
            Response.Redirect(Request.RawUrl);
        }

        protected void DeleteItem(string id)
        {
            var table = NivoSettings.ImageData.GetDataTable();
            
            for (int i = 0; i < table.Rows.Count; i++)
            {
                if (table.Rows[i][Constants.UID].ToString() == id)
                {
                    foreach (ExtensionParameter par in NivoSettings.ImageData.Parameters)
                    {
                        par.DeleteValue(i);
                    }

                    ExtensionManager.SaveSettings(Constants.ExtensionName, NivoSettings.ImageData);
                    break;
                }
            }

            // delete image
            string[] uid = id.Split(':');
            if (uid.GetUpperBound(0) < 1) return;

            var folder = Server.MapPath(Blog.CurrentInstance.StorageLocation + Constants.ImageFolder);
            var imgPath = Path.Combine(folder, uid[1]);
            File.Delete(imgPath);
        }

        private void Upload()
        {
            var folder = Server.MapPath(Blog.CurrentInstance.StorageLocation + Constants.ImageFolder);
            
            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);

            txtUploadImage.PostedFile.SaveAs(Path.Combine(folder, txtUploadImage.FileName));
        }
    }
}

sldr-adm

Looks like we are ready for prime time – in the next step we’ll package and upload extension to the gallery so that other people can download and use it.

Comments (11) -

Andy McKay
Andy McKay
9/20/2011 7:54:23 AM #

Another well crafted and useful tutorial. Apart from the fact you get the very tidy Nivo slider, there's a good few BlogEngine tips and techniques that you can apply elsewhere. Part 1, for those of us who didn't know, BE protects files in the App_Data folder and auto includes JavaScript and CSS in the Scripts and Styles folders. Part 2, some good background info that explains use of controls and extensions (with nice example on using Regex) and touching on Razor. Part 3, The icing on the cake, how to use the extension manager and custom pages to handle file uploading and persistence, just the example I needed for use on a completely different extension I've been procrastinating over writing.

The only thing I would add is a little more commenting in the code to enhance the narrative for punters like myself who like the spoon feeding.

P.S. strings are evil :-)

Andy McKay
Andy McKay
9/21/2011 5:48:30 PM #

Hi Ruslan

Are the comments working?

rtur.net
rtur.net
9/21/2011 7:20:56 PM #

Once in a blue moon Akismet makes mistakes too. Corrected.

DupeFree Pro
DupeFree Pro
11/11/2011 1:42:24 AM #

Hi,nice visit here.

David
David
11/16/2011 4:27:24 AM #

good !!!

gerardo
gerardo
12/23/2011 1:04:40 PM #

Hi!

I downloaded and installed.

What I failed to understand is how to access to NivoSlider Admin page from the Admin Control Panel where you see tabs for Dashboard, Posts, Comments, etc.

I was expecting to see another tab named "NivoSlider".

In VS2010, I right-click the \User controls\NivoSlider\Admin.aspx and click on browse, then I can see the Settings:Slider page inside the Admin Control Panel. The only problem is that it isn't hosted by a tab. So you click on anothe tab, Dashboard for example, then you can not access the NivoSlider page anymore.

So, to summarize the bug, a new tab is missing to host the NivoSlider Setting.

Thanks!

rtur.net
rtur.net
12/23/2011 6:54:42 PM #

It is an extension - so go to "extensions" tab and you'll see a menu item in the right bar for slider admin page.

Brian Davis
Brian Davis
1/22/2012 2:38:26 AM #

gerardo :I was expecting to see another tab named "NivoSlider".
In VS2010, I right-click the \User controls\NivoSlider\Admin.aspx and click on browse, then I can see the Settings:


I am having the same problem.

I go to Plugins --> Extensions its not there on the right side.

Screen Shot:

http://www.diigo.com/item/image/17qct/5p1e

Tried this on Different versions of be same thing.

BE 2.5.0.15
   2.5.0.27

rtur.net
rtur.net
1/24/2012 9:41:05 AM #

Hmm. I'll have to take a look at it.

Brian Davis
Brian Davis
1/26/2012 3:26:51 PM #

It works now.  I was working on this locally on my computer and not on a live server.

Now it works.

Don't know how through.

Brian Davis
Brian Davis
1/26/2012 3:28:13 PM #

Now just need to know how to get the button from the top to go to the bottom and how to change the Transition Effects.


Comments are closed