Squarespace featured on High Scalability Blog

The High Scalability Blog featured Squarespace recently and it was interesting overview of some of the technology that my site runs on.  I have to admit that I feel a bit guilty not running on Dotnetnuke or another Microsoft platform but I do like the ease of use of Squarespace.    I encountered Squarespace much the same way that Todd @ High Scalability did, through advertising on Twit.TV.  

My main wish is that Squarespace was a bit more extensible.  I'd like to add my own modules.  I'd probably even plug through Java to do it if I had to.  I'd really like to overhaul the Kingdom of Atenveldt Order of Precedence and put it on a platform that runs fast and is consistantly reliable.  This weekend I plan on spending some time on the site, upgrading to the latest version of DNN and maybe working on the skin a bit.  I have had a couple of designers show interest in helping me make a skin but nothing has ever come through with it. Maybe I can bring some more life back to the DNN based Atenveldt site with a bit of TLC. (tender loving care, not the learning channel... )

 

Altering the Look of the DNN FAQ Module

I had an issue with the way that the DNN module for FAQ's was behaving. Some extra space was appearing around my lists of questions and breaking up the way the page was supposed to look.  It turns out that the questions were getting these extra <p> tags around them.  Unfortunately, because the <p> tags were unexpected, the FAQ module was doing strange things like wrapping the <p> tags with <b> tags (it also wraps the whole thing in an <a> tag too to open up the question), which isn't really proper HTML. I was able to edit the template of the FAQ Module to stop the <b> tags but there was still the issue of the wierd spacing that was being created by the <p> tags.

The FAQ module was originally written against a different default rich text editor than what DNN ships nowadays (FCKeditor).  The old editor wouldn't add <p> tags to the text of your questions.  The FCK editor does and although it can be disabled by changing a config file, there are good reasons for keeping it, such as keeping your text wrapped correctly for proper HTML.  Using this config change would also change the behavior for the rich text editor through out all portals and modules, which would have bad side effects.

An option I have seen posted in a couple of places was to use the plain text module, but the problem with that is that changing that option seems to change all editor boxes sitewide, including the answer box.  Editing the source and leaving it without the <p> tags works but is not a good option because there are these special set of instructions for any editor who happens to want to update a question. This jumping through hoops isn't a good idea.

The solution is style sheets. Using Firebug, I could easily determine that the FAQ module wraps itself in a div with the class DNN_FAQsContent.  Now I can affect the <p> tags style within this div without affecting <p> tags throughout the site.  I decided to use the portal.css because I have several portals and I didn't want to unexpectedly affect them.  I was also pretty sure that I would want these effects even if I later changed skins.  So I didn't want to change the skin.css.

 

/* MODULE-SPECIFIC */
.DNN_FAQsContent a.SubHead p
{
    display:inline;
    margin: 0px 0px 0px 0px;
}

 

Let me explain what I did.  Since the specific <p> tags I wanted to attack were in the div with the class DNN_FAQsContent I started with that identifier.  The <p> tags were also (probably improperly) surrounded by an <a> tag with a class of SubHead.  This was all very easily discovered by using Firebug.  I wanted to make sure I wasn't affecting the <p> tags that are in the answers.   Once I used the CSS to identify the tags I wanted to change, I applied the changes.  display:inline changes the <p> tag from being a block element to being an inline element.  This is more along the lines of how the the FAQ module originally displayed the question.  I also took out any margins.

SCA Online OP Improvements, Link to Dynamic Forms Module - Part 1

A lot of coding blogs write about problems they have already solved and while I know its not unique to do so, I want to write down stuff as I do them for this project, rather than summarizing a problem I have already solved. Although this series of blogs involves the SCA, it will be more about the process or working on a DNN (DotNetNuke) module and the programming that goes into it.

If you are not familiar with the SCA Online OP, it is a module I wrote and maintain for the Kingdom of Atenveldt, a branch of the SCA.  The OP part of the module stands for Order of Precedence.  This is basically a ranking system that we use in the SCA loosely based on historical practices of knowing who is ranked above who.  I have taken that practice and put a lot of work around it to not only list the people and what awards they have in the right order, but enable viewing pictures of the people, descriptions of the awards, information about the local groups the people reside in and information about the members that reign over them.  All of this can be found on the Atenveldt Online OP.

Since I will have a lot of free time this memorial day weekend, I am putting some effort into some improvements I have wanted to do to the module for some time.  The first improvement I want to do is to link the OP to the new forms module I just installed for making an award recommendation for a person.  The forms module supports having a field get data out of a querystring, so it should be a simple task of creating a dyamic link that will point from a person's record in the OP to the award recommendation form.  Even though this module will never be in widespread use and will most likely be a single installation on the Atenveldt site, I try to add features that stay flexible and have the possibility of transferring over to anther DNN installation.

Step 1.  Settings.

I figure the module needs to be pointed to from the settings.  I am imaging a checkbox that indicates that we want to have a reference link to the award rec. form, along with a drop down of all the installed Dynamic Forms modules installed in the portal.

I have a settings page already created. So I will add the controls to the form.  Here is what the settings form looks like.

<%@ Control AutoEventWireup="false" CodeFile="SCAOnlineOPSettings.ascx.cs" Inherits="JeffMartin.DNN.Modules.SCAOnlineOP.SCAOnlineOPSettings"
    Language="c#" %>
<%@ Register TagPrefix="dnn" TagName="Label" Src="~/controls/LabelControl.ascx" %>
<table>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:label id="dlBasePortal" runat="server" controlname="ddlPortalList" />
           </td><td valign="top">
            <asp:DropDownList runat="server" ID="ddlPortalList">
            </asp:DropDownList>
        </td>
    </tr>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:Label ID="dlLinktoReccomendationForm" runat="server" ControlName="chkLinkToRec" />
        </td>
        <td valign="top">
            <asp:CheckBox ID="chkLinkToRec" runat="server" />
        </td>
    </tr>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:Label ID="dlReccomendationForm" runat="server" ControlName="ddlDynamicForms" />
        </td>
        <td valign="top">
            <asp:DropDownList runat="server" ID="ddlDynamicForms">
            </asp:DropDownList>
        </td>
    </tr>
</table>
 

 

Now that the form fields are created, I will put some backing logic to them.  First I need to fill the drop down list with all the modules that are the DataSprings Dynamic Forms that I am looking for.  As I am writing this code, I realize that I want to put a message indicating that I can't detect the Dynamic Forms module being installed.   This will be convenient to test as I don't have the dynamic forms module installed yet on my local build installation.   As I look at the code a bit more, I decide to just hide the setting if I can't detect the module existing.  So I put a placeholder module around the two table rows that contain the info needed for setting up the dynamic forms stuff.

 

ModuleController mc = new ModuleController();
ModuleInfo mInfo = mc.GetModuleByDefinition(PortalId, "Dynamic Forms");
if (mInfo == null)
{
    phAwardRecsSettings.Visible = false;
}
else
{
    phAwardRecsSettings.Visible = true;
    ArrayList moduleTabs = mc.GetModuleTabs(mInfo.ModuleID);
    ddlDynamicForms.DataTextField = "ModuleTitle";
    ddlDynamicForms.DataValueField = "TabId";
    ddlDynamicForms.DataSource = moduleTabs;
    ddlDynamicForms.DataBind();
}

 

 You may notice I am programming in C#.  This is just something I would rather do than keep flipping between VB for this and C# at work.  The modules work perfectly fine. 

The next step for me is to try this out.  I don't like going to far without making sure this code works.  This keeps it fresh in my mind if I have to debug it rather than debugging something I did an hour ago if I wait that long before testing it out.  After testing that nothing was different when I didn't have the dynamic forms installed, I installed the dynamic forms module, imported my form from the live website, and tested again... It works!

 New Settings Display Correctly

Now that everything is displaying correctly - I need to actually make the settings save (and be read).  I want to make sure the drop down box shows the correct value when the settings are loaded and the check box is correctly checked or unchecked.  Here are the methods:

public override void LoadSettings()
       {
           try
           {
               if (!Page.IsPostBack)
               {
                   var basePortalId = ((string) Settings["BasePortal"]);
                   if (basePortalId == null)
                       ddlPortalList.SelectedValue = "0";
                   else
                       ddlPortalList.SelectedValue = basePortalId;
 
                   var awardRecTabId = ((string) Settings["AwardRecTabId"]);
                   if (awardRecTabId != null)
                       ddlPortalList.SelectedValue = awardRecTabId;
 
                   bool linkToRec = Convert.ToBoolean(Settings["LinkToRec"]);
                   chkLinkToRec.Checked = linkToRec;
               }
           }
           catch (Exception exc)
           {
               Exceptions.ProcessModuleLoadException(this, exc);
           }
       }
 
       public override void UpdateSettings()
       {
           try
           {
               var objModules = new ModuleController();
               objModules.UpdateModuleSetting(ModuleId, "BasePortal", ddlPortalList.SelectedValue);
               objModules.UpdateModuleSetting(ModuleId, "AwardRecTabId", ddlDynamicForms.SelectedValue);
               objModules.UpdateModuleSetting(ModuleId, "LinkToRec", chkLinkToRec.Checked.ToString());
 
               Response.Redirect(Globals.NavigateURL(), true);
           }
           catch (Exception exc)
           {
               Exceptions.ProcessModuleLoadException(this, exc);
           }
       }

When I go to test, doh... there is a problem.

DNN Critical ErrorLets see what the Event Log has to say about that. 

It turns out after quite a trip down the debugging rabbit hold that the above code doesn't compile in ASP.NET 2.0 (I may upgrade my site shortly).  The 'var' keyword got slipped in there by an overzealous ReSharper reformat.  After changing the settings in ReSharper, fixing the 'var', I found that the LoadSettings() actually gets called BEFORE the Page_Load... this means that I need to bind my controls in the LoadSettings().  Just because I am a bit paranoid about this changing in some future version, I put in a BindControls() method that I can call from Page_Load too.  I also protect for binding twice unnecessarily.  The final code for the settings is below.

using System;
using System.Collections;
using DotNetNuke.Common;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Services.Exceptions;
 
namespace JeffMartin.DNN.Modules.SCAOnlineOP
{
    partial class ScaOnlineOpSettings : ModuleSettingsBase
    {
        #region Web Form Designer generated code
 
        protected override void OnInit(EventArgs e)
        {
            //
            // CODEGEN: This call is required by the ASP.NET Web Form Designer.
            //
            InitializeComponent();
            base.OnInit(e);
            this.Load += new System.EventHandler(this.Page_Load);
        }
 
        /// <summary>
        ///        Required method for Designer support - do not modify
        ///        the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
        }
 
        #endregion
 
        private void Page_Load(object sender, EventArgs e)
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    BindControls();
                }
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
 
        private bool boundOnce;
 
        private void BindControls()
        {
            if (!boundOnce)
            {
                PortalController pc = new PortalController();
                ArrayList Portals = pc.GetPortals();
                ddlPortalList.DataTextField = "PortalName";
                ddlPortalList.DataValueField = "PortalID";
                ddlPortalList.DataSource = Portals;
                ddlPortalList.DataBind();
 
                ModuleController mc = new ModuleController();
                ModuleInfo mInfo = mc.GetModuleByDefinition(PortalId, "Dynamic Forms");
                if (mInfo == null)
                {
                    phAwardRecsSettings.Visible = false;
                }
                else
                {
                    phAwardRecsSettings.Visible = true;
                    ArrayList moduleTabs = mc.GetModuleTabs(mInfo.ModuleID);
                    ddlDynamicForms.DataTextField = "ModuleTitle";
                    ddlDynamicForms.DataValueField = "TabId";
                    ddlDynamicForms.DataSource = moduleTabs;
                    ddlDynamicForms.DataBind();
                }
                boundOnce = true;
            }
        }
 
 
        public override void LoadSettings()
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    BindControls();
                    string basePortalId = ((string) Settings["BasePortal"]);
                    if (basePortalId == null)
                        ddlPortalList.SelectedValue = "0";
                    else
                        ddlPortalList.SelectedValue = basePortalId;
 
                    string awardRecTabId = ((string) Settings["AwardRecTabId"]);
                    if (awardRecTabId != null)
                        ddlPortalList.SelectedValue = awardRecTabId;
 
                    bool linkToRec = Convert.ToBoolean(Settings["LinkToRec"]);
                    chkLinkToRec.Checked = linkToRec;
                }
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
 
        public override void UpdateSettings()
        {
            try
            {
                ModuleController objModules = new ModuleController();
                objModules.UpdateModuleSetting(ModuleId, "BasePortal", ddlPortalList.SelectedValue);
                objModules.UpdateModuleSetting(ModuleId, "AwardRecTabId", ddlDynamicForms.SelectedValue);
                objModules.UpdateModuleSetting(ModuleId, "LinkToRec", chkLinkToRec.Checked.ToString());
 
                Response.Redirect(Globals.NavigateURL(), true);
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
    }
}

Now that the settings are in and saved, I actually have to make use of them in the module.   I will save that for Part 2.

Google Apps Mail with DotNetNuke

I was recently having a problem with the mail on the Atenveldt.org site.  I had switched from the host provided email server to Google Apps email, but in some cases email was being delivered to the wrong place.   It turned out that this was because I had the host's email server still set up in the SMTP settings in DotNetNuke.  The aliases on this domain change quite a bit as different officers take over and they keep the same alias but point to a new destination address.  For instance, the King and Queen of Atenveldt rotate twice a year, the crown (at) atenveldt.org address will always be available but point to the new King and Queen as they take over from the old set. 

The issues presented themselves when the DotNetNuke (or a particular module) was trying to send to someone at an atenveldt.org address. The host's email server thought it was still the email service for that domain and didn't bother to check DNS to see if that was still true, so the people who were set up in the old alias were getting that email rather than the people we had changed over to get email from the Google Apps group that we set up.

I couldn't just have the host take off the domain from the server because then the site wouldn't be able to email because it won't relay.

The solution to this was to use Google's SMTP server.  Once I set up a google apps account that was specific to the website, I had DotNetNuke use those credentials to log in to the SMTP server and send email. The email coming from the site would appear to be coming from website (at) atenveldt.org (even if a module tried to send out from a different address.  This is an effect of using the google smtp.)  A potential security hole exists in that all mail sent is now being saved in the website's google apps email box.  This means that when the DNN site sends out passwords and such, a record of it will be in that account's sent items. 

The benifits of using Google Apps for this setup is pretty nice.  Our mail is no longer tied to the host, so if we decide to leave this host and go to a new one, email will be one less thing we have to worry about.  There are some other benefits to the Google Apps for our organization in terms of shared calendar and docs.