Archive for the 'Work' Category

Using DNN OpenForm

Sacha Trauwaen’s OpenForm is a module for DNN which is used for adding forms to a page.

It requires that the OpenContent module is installed first. The configuration is based on AlpacaJS.

Official documentation

Creating a new form

  1. Browse to the page which will hold the form and switch to edit mode
  2. Add a module
  3. Choose OpenForm
  4. Click Template Exchange
    1. Under Action, choose Copy Template
    2. Under From Template, choose an existing form (e.g. “Site:Contact”)
    3. Under To New Name, type the name of the new form (e.g. “Further Information Request”)
    4. Press Copy
  5. When you see Copy Successful, close the modal box and wait for the page to refresh
  6. Under Choose a template, choose Site:Whatever-you-just-called-it and wait for the page to refresh
  7. Click Template Settings
  8. Here you can:
    • Set the Message after Submit that appears after the user successfully sends the form
    • Add Email Notifications (see section below)
    • Add other things (Tracking script / reCaptcha keys)
    • Click Save
  9. Hover the module, open the Pencil menu and choose Form Builder
  10. Add your fields (see section below) and click Save

Form Builder

  1. Edit the page
  2. Hover the module, open the Pencil menu and choose Form Builder
  3. Give it a moment to load the left column, it can be a bit slow

From here you can add and remove items to the form. The left column shows the form builder, while the right shows a preview (albeit using plain bootstrap styles, so it’s not a true WYSIWYG).

  • To add a control, click the Plus icon
  • To remove a control, click the Minus icon
  • To move a control up, click the up arrow
  • To move a control down, click the down arrow
  • To edit a control, click its name.

    • From here, set the field name (no spaces or special characters)
    • The type
    • The Label, which will appear on the screen
    • Required
    • Advanced (default value, helper text, placeholder text, position if this is a multi-column form)
    • Values (if this is a drop-down, radio buttons, or multi-select checkboxes)
    • Dependencies (this control can be dependent on the value of other controls)
  • Save or Cancel your changes

Note: It’s a good idea to press Save often, as it can occasionally get confused when moving new un-named controls up and down.

Form Settings

You can reach the form settings (for email notifications, etc.) by:

  1. Edit the page
  2. Hover the module, open the Pencil menu and choose Form Settings

Message after submit

You have a WYSIWYG editor to control the content shown after the form is submitted. To include form data in the message, you can use special tokens.

e.g. If your form contains fields named name and email, put this into the WYSIWYG:

Thanks {{ name }}, your email address is {{ email }}.

Email notifications

To make it send an email notification, you need to:

  1. Edit the page
  2. Pencil > Form Settings
  3. Add an email notification

Note: If the emails start failing, you can look at the Admin Logs in the PersonaBar.

The message uses a WYSIWYG to allow you to customise the HTML email you send back. You can use the same tokens as the Message After Submit to customise it, e.g.

Thanks {{ name }}, your email address is {{ email }}.

There is also a special {{{ FormData }}} token, which outputs all of the data submitted. See https://openform.readme.io/docs/getting-started#section-email-messages-and-user-feedback

You can add multiple email notifications. This means you can send a notification back to the person who filled in the form another to the staff members who need to receive the information, and yet another to a CRM system.

Pre-filling the form with querystring data

You can use Javascript to pre-fill the form with data from the querystring. See https://openform.readme.io/docs/prefill-form-with-query-parameters for an example.

Pre-filling the form with user data

If the current user is logged in, you can pre-fill the form with their profile information. Make sure you use the following field names in your form:

  • Username
  • FirstName
  • LastName
  • Email
  • DisplayName
  • Telephone
  • Street
  • City
  • Country

See https://openform.readme.io/docs/getting-started#section-auto-initialization-of-fields-from-dnn-user-when-logged-in

Viewing form submissions

As well as being emailed out, all form submissions are stored in the database. If you have admin or super user access, you can view them by:

  1. Edit the page
  2. Hover the module, open the Pencil menu and choose Submissions

From there you can download the data in Excel format.

If you have superuser permissions, you can get to the raw JSON data:

  1. Edit the page
  2. Hover the module, open the Pencil menu and choose Edit Raw Data

At the time of writing, you cannot delete form submission data. See https://openform.readme.io/v03.02.00/discuss/5bfff3f5261e9d03e601f2b6

Advanced features

Certain features can only be reached by directly editing the config files. For instance, some features (such as multi-page forms) cannot be added using the Form Builder. There is also the ability to use custom CSS and Javascript on the module, and a C#/Razor post-submission message which can do more than the regular submission message (with full access to the DNN API).

If you have Super User access, you can get to these by:

  1. Edit the page
  2. Hover the module, open the Pencil menu and choose Edit Template Files

If you’re developing a new form on your development machine, you may find it easier to find the files on the file system. They’re stored in:

[path to site]\Portals\[portal id]\OpenForm\Templates

So on my dev machine, where I’ve used nvQuickSite to install DNN, they’re here:

C:\Websites\sesame\Website\Portals\0\OpenForm\Templates

And on an Azure App Service, they’re likely to be under:

site\wwwroot\Portals\0\OpenForm\Templates

DNN Liquid Content – Getting the first item in a visualizer

Sometimes, for example when working with a Bootstrap carousel, you need to know when you’re on the first item in the visualizer. Liquid Content apparently can’t do this natively (yet) so you’ll need to do this with Javascript.

To add the active class to the first item in a Bootstrap carousel, I put the following code into the script section of the visualizer. You should be able to adapt it to suit your needs:

var items = document.querySelectorAll(".carousel");
if (items.length) {
items.querySelector(".carousel-item:first-child").classList.add("active");
}

I raised this as an enhancement request with DNN Software, as DNN-26912.

DNN Liquid Content – Getting the content item id in a visualizer

To get a unique id for a content item, you simply use {{id}}, in the template part of the visualizer editor.

For example:

<article class="news-headline" id="{{id}}">

It’ll come out as something like:

<article class="news-headline" id="aa1a93b2-cca4-4a32-bc0a-2c52a1b28019">

If you need to link to it, e.g. for a Bootstrap modal, use something like this:

<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#{{id}}">

It comes out like:

<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#aa1a93b2-cca4-4a32-bc0a-2c52a1b28019">

The ID is tied to the specific content item, so you can use it across multiple visualizers (e.g. put all the buttons in one visualizer, and all the modals into another one).

This might seem obvious, but I didn’t find it in the documentation.

DNN Liquid Content quick tips

In DNN Evoq, there’s a structured content solution called Liquid Content. The visualizers used to put that content on a DNN web page use the Liquid template language (I believe it uses dotliquid under the hood). Here’s a few things that’ve been useful to me:

If statement with a checkbox value

Sometimes my users want to be able to choose whether a link opens in a new tab. So I added a checkbox to the content type. Now, I expected it’d be as simple as {% if {{imgOpenInNewTab}} %}target="_blank"{% endif %}. Sadly it wasn’t.

After quite a lot of fiddling, I discovered I had to use the first filter, like so:

{% if {{imgOpenInNewTab.first}} == 'Yes' %}target="_blank"{% endif %}

Working with images

When you want to add an image to the template, add one to your content type and then add it to the template, like so: {{ image }}. It’ll output the entire image tag for you.

But what if you want to do something with the img element? Maybe set your own alt text, or specify other HTML attributes on there. This is where the image_url filter comes in. It returns the URL of the image. You’d use it like this:

<img src="{{ image | images_url }}" alt="" />

Working with absolutely massive images

Sometimes the people putting content into the system will upload a 3000px image from somewhere like Unsplash. DNN has the facility to scale images using the DNN Image Handler (and cache them on the server). You’d set the img src to something like /DnnImageHandler.ashx?mode=file&w=700&resizemode=fit&file=path-to-file. How do we use this with Liquid Content? It’s rather tricky, but it can be done, making use of lots of Liquid Template loops, variables and filters:

Firstly, the Image Handler doesn’t work with SVG images, so first we’ll need to figure out if the image is an SVG:

{%- assign file_extension = image | images_url | split: "." | last | split: "?" | first -%}

Then we need to get the server-local path to the image, (i.e. strip the https://example.com/ off the front). This is a bit complicated:

{%- assign local_image_url = image | images_url | remove_first: "http://" | remove_first: "https://" | split: "/" -%}

Then, pass it to the DNN image handler, stripping off the querystring as we go:

<img src="/DnnImageHandler.ashx?mode=file&w=700&resizemode=fit&file={%- for item in local_image_url -%}{%- unless forloop.first -%}/{%- if forloop.last -%}{%- assign not_qs = item | split: "?" -%}{{ not_qs.first }}{%- else -%}{{item}}{%- endif -%}{%- endunless -%}{%- endfor -%}" alt="" />

Finally, sometimes when an image has funny chracters in the filename it ends up as an aspx file, so we can’t do this at all. So all in, we end up with this:

{%- assign file_extension = image | images_url | split: "." | last | split: "?" | first -%}
{%- assign local_image_url = image | images_url | remove_first: "http://" | remove_first: "https://" | split: "/" -%}
{%- if file_extension == "svg" -%}
    <img src="{{ image | images_url }}" alt="" />
{% elsif file_extension == "aspx" %}
    <img src="{{ image | images_url }}" alt="" />
{%- else -%}
    <img src="/DnnImageHandler.ashx?mode=file&w=700&resizemode=fit&file={%- for item in local_image_url -%}{%- unless forloop.first -%}/{%- if forloop.last -%}{%- assign not_qs = item | split: "?" -%}{{ not_qs.first }}{%- else -%}{{item}}{%- endif -%}{%- endunless -%}{%- endfor -%}" alt="" />
{%- endif -%}

Now that might all seem a bit overkill, but if your content editors are prone to uploading 3000px images from Unsplash, this helps deal with the problem.

A DNN shout-out

I’ve started building websites using DNN recently. There’s been quite a learning curve, but there’s some amazing tools and resources which have made life a hell of a lot easier for me. So here’s a shout out:

  • nvQuickSite is a brilliant little tool which makes firing up new installations of DNN a breeze
  • nvQuickTheme (from the same people) is a great place to start when building a new DNN Theme or Skin
  • OpenContent (by Sacha Trauwaen) is a brilliant module for building structured content in DNN. If, like me, you’ve spent a lot of time working with SharePoint, you can think of OpenContent as Content Types, Lists and the Content Query Web Part, but a hell of a lot nicer. As much as I loved XSLT, I don’t miss it using it – I can choose between Handlebars and Razor here, depending on how complex things need to get. I’ve used it for all sorts of stuff – from news feeds to video galleries and “meet the team” modules.
  • Matt Rutledge’s Yo DNN Generator has come in really handy for building new DNN modules from scratch. Same is true of Chris Hammond’s Visual Studio Templates.
  • The DNNConnect group on Facebook is full of really helpful people who know DNN inside out.
  • Aderson Oliviera’s Youtube Channel and Open Friday initiative.

I’m sure there’s more I’ve forgotten. I feel pretty proficient with DNN now, but I couldn’t have got there without the help from all of the people and tools here. Thanks y’all.

Using the DNN Services Framework API with fetch

When building an SPA mdoule in DNN (formerly Dotnetnuke), you’ll likely find yourself using the DNN WebAPI. It turns out the Javascript API for this is very XHR-centric. I wanted to use the fetch API instead.

When using fetch, instead of using beforeSend: setModuleHeaders() you’ll need to build the headers object yourself:

fetch(url, { headers: {
    "ModuleId": your-module-id-here,
    "TabId": your-tab-id-here,
}})

I modified this dotnetnuclear code to look more like this:

let service = {
    path: "PathToMyModule",
    framework: $.ServicesFramework(sbgQuestionnaireModule_Context.ModuleId),
    controller: "MyController"
}
service.baseUrl = service.framework.getServiceRoot(service.path);
// Set the headers
service.headers = {
    "ModuleId": service.framework.getModuleId(),
    "TabId": service.framework.getTabId()
};
// Set the anti-forgery token
if (service.framework.getAntiForgeryValue()) {
    service.headers["RequestVerificationToken"] = service.framework.getAntiForgeryValue();
}
service.loadUrl = service.baseUrl + "/" + service.controller + "/load";
fetch(service.loadUrl, {
    headers: service.headers
})
// etc

(Thanks to Daniel Valadas for his help getting started with this)

The great Internet Explorer 8 controversy

So, the Internet Explorer team has proposed that as of IE8, if you want the latest and greatest features you’ll have to opt-in. (Note: Microsoft have changed their mind.) You can do this by way of an http-header, or using a meta-tag:

<meta http-equiv="X-UA-Compatible" content="IE=8" />

I can see understand why they’ve chosen this direction. IE6 was absolutely chock-full of bugs, but was left to stagnate for so long that web-developers began to rely on it’s quirks in order to make pages render correctly. Eventually IE7 came along and fixed many of those bugs. Consequently, many pages that were reliant on IE6 bugs broke in IE7. Microsoft don’t want to see that happen again.

The rest of the world doesn’t seem so keen on the idea. The web has gone wild, shouting about the myriad technical problems. Representatives from Mozilla (Firefox/Gecko), Apple (Safari/Webkit) and Opera have all said they don’t like the idea (and won’t be implementing it in their browsers). The big issue that stands out for me isn’t technical at all though. It’s education.

Getting the word out

Somehow, Microsoft need to get the word out to existing web designers and developers. They need to tell newcomers to the industry. They need to let educators know. I’m struggling to see how they’re going to do that. Why?

A quick look around the SitePoint forums reveals that people are still tripping up on using the doctype element to switch between quirks and standards modes (the last attempt at providing backwards compatibility to legacy web pages). They were first introduced with Internet Explorer 5 for Mac the best part of a decade ago. Over the years, every major browser has taken up the technology, countless people have blogged about it, written tutorials on it, put it into knowledge bases, included it in web design books, podcasted it, and people are still struggling to get their heads around it.

I reckon Andy Budd hit the nail on the head:

No matter what great leaps forward the Internet Explorer team make from now on, the majority of developers won’t use them and the majority of users won’t see them. By doing this the Internet Explorer team may have created their own backwater, shot themselves in the foot and left themselves for dead.

Things move quickly on the web

Of course, while I was writing that, the story developed a bit further.

It turns out that using the new HTML5 doctype will trigger the new super-standards-mode in Internet Explorer 8. What’s more, Ian Hickson thinks he knows a way to make an HTML5 compatibility layer for IE7 (see the last paragraph).

My interpretation? Microsoft are trying to make HTML4 and XHTML1 legacy formats (unless you specify otherwise with the X-UA-Compatible header) and push HTML5 as the standard for content going forward. I’ll be very interested to see how all of this plays out.

Lemurs

Katemonkey has gone and rendered everything I’ve written here irrelevant: The “X-UA-Compatible” Controversy — As portrayed by toy lemurs.

Some time later…

Microsoft have decided to do the right thing: IE8 now will use standards-mode by default.

Zoom

Web accessibility can be hard to get your head around. It’s all very well talking about best practise, but without personal experience it can be very difficult to understand the day-to-day issues people face.

I’m lucky, in that my eyesight is still 20/20. Yet today I ran head-on into a common web accessibility barrier. I got a (diluted) taste of what it’s like to use a screen magnifier to browse the web (like many vision-impaired users).

I was playing on the Wii and when I’d had enough of Super Mario Galaxy for the day, I jumped over to The Internet Channel (or Opera for Wii as us web monkeys know it).

I loaded Google Mail. Alas I have a relatively small television by today’s standards, so the on-screen text was rather small. Thankfully, on the Wii it’s very easy to zoom in on a certain parts of the screen, so I did. I scrolled across to the Labels part of Google Mail and clicked one. Just as you’d expect, it updated the conversations part of the page. No problem.

Well, no problem except for the whole zoomed in bit. Because the site is built using Ajax, there hadn’t been a full-page refresh. It meant I had no way of knowing something had happened elsewhere on the page until I zoomed out again.

Now, Google also offer basic HTML versions of their web applications. These don’t use Ajax, so you get the full-page refresh (and hence you’re aware that the page has changed). That’s one way to solve the problem, but creating separate web applications for different groups of users isn’t always an option.

I’m not saying Ajax is a bad thing — rather pointing out one of it’s side effects. I’m not yet sure how I’d work around the problem (and I’d love to hear suggestions), but it’s certainly food for thought when designing for the web.