Adding JavaScript to a DNN page is easy: use the Text/HTML module. However, if you want to enhance an existing DNN module with your own client-side script you may prefer to add it in the module's FOOTER field. To illustrate, we'll use the SURVEY module to create a "pick three items" survey that REQUIRES the user to pick three and only three items...
*** NOTE: THIS POST APPLIES TO SURVEY MODULE VERSION 4.5. ***
Module Settings: HEADER and FOOTER
Every DNN module has a HEADER and a FOOTER field under SETTINGS->ADVANCED SETTINGS. These are free-form HTML fields that will add code just above the module's rendered page code (HEADER) or just after the rendered code (FOOTER). If you want to add JavaScript that references the module's page content (for reading or adding/updating) the FOOTER is an excellent choice as any code placed there will not execute until after the module's initial page code has been rendered.
SURVEY Module
DNN provides a SURVEY module for administering simple surveys. A survey can have multiple questions but the method of user input for each question is limited to either radio buttons (select one answer) or groups of checkboxes (select one or more answers). There is also an option to designate an answer option as the "correct answer" which allows the SURVEY module to be used to administer simple tests.
*** NOTE: THIS POST APPLIES TO SURVEY MODULE VERSION 4.5. ***
Compared to third-party module offerings, the limitations of the SURVEY module are many. Bear in mind, though, that SURVEY has always intended to be a simple, easy-to-use module without a lot of frills. Thankfully we can easily add JavaScript to enhance the module without changing any module code.
Our example will add an enhancement to turn a "select one or more answers" survey into a "select exactly three answers" survey. A real-world example would be a "vote for your favorite three items" survey.
Sample SURVEY: Pick Your Favorite Three Pizza Toppings
Let's get started. Add the SURVEY module to a page and then select "Add Question". Our question will be "Pick your three favorite pizza toppings." Next, select "Multiple Selection" for the "Type" setting. Now we add our answer options. Add each of the following (one at a time) to the "New Option" field, clicking "Add Option" to add the option to the list of "Options":
Cheese
Ham
Hamburger
Mushrooms
Olives
Onions
Pepperoni
Sausage
Finally, click the "Update" link at the bottom to save our one-question survey. Done!
Now we have a working survey that allows users to select their three favorite toppings. Unfortunately, the survey allows users to pick any number of toppings. We want to require users to select exactly three toppings. There is no provision for this in the SURVEY module but we can add it using JavaScript.
What our script will do
As with most things, there are multiple ways to achieve our goal. For this example we will dynamically grab the collection of possible answers (checkboxes) rather than explicitly refrence each checkbox by its ID value. As far as our code is concerned, we won't even care if each checkbox even has an ID.
To get the collection of checkboxes we'll use the "getElementsByTagName" method to grab all of the INPUT elements within our survey. To insure that we're getting only elements within our survey, though, we'll need to hard-code the ID value of the survey question's parent TABLE element (CSS pundits will note that this is yet another example of a DNN module relying on a TABLE for layout instead of data). We must restrict our domain to elements within our survey question table so that we're sure to not affect (or be affected by) other INPUT elements on the page.
Once we have the collection of INPUT elements (checkboxes), we'll iterate through and dynamically add an "onClick" event to each checkbox. The "onClick" will call a function that counts the number of "checked" boxes in our collection. If we have less than three boxes checked, the "Submit Survey" link will be hidden. If we have exactly three boxes checked the "Submit Survey" link will be shown. If we have more than three boxes checked the forth "checked" box will be unchecked, we'll pop up an alert box to tell the user that only three options can be selected and the "Submit Survey" link will be shown (because we'll have three items checked).
The Code
Let's skip right to the instant-gratification phase which we'll follow with some code commentary. Most of the code is self-explanatory via inline comments but a couple of explanations are needed to fully understand things.
Step 1: Get Your ModuleID value
Go ahead and go into your module SETTINGS. Before we do anything further, though, look at the page URL. Near the very end of the URL there should be "/ModuleID/" followed by a number which is then followed by "/default.aspx". Make a note of that number just after "/ModuleID/" -- that is our SURVEY module's ID number. In the code below, insert that number where you see [your module ID here].
Step 2: Drop in the code in your module's FOOTER field under ADVANCED SETTINGS. Note that you will need to remove the extra space just after the "<" and just before the ">" in the opening and closing SCRIPT tags.
< script >
// Get reference to SUBMIT link element for this survey
var submitElem = document.getElementById('dnn_ctr[your module ID here]_Survey_cmdSubmit');
// Get parent TABLE element for this survey question
var rootElem = document.getElementById('dnn_ctr[your module ID here]_Survey_lstSurvey_ctl00_chkOptions');
// Get collection of INPUT tags
var chkCol = rootElem.getElementsByTagName('INPUT');
function chkCount()
{ // Count the number of currently-checked items and take appropriate
// action based on the total checked items.
// Hide SUBMIT link. Reveal only if exactly three items are checked.
submitElem.style.visibility = "hidden";
tmpTotal = 0; // Number of checked options.
lastChecked = -1; // The final checked option ('-1' = 'nothing')
for (y=0; y// Check status of checkboxes
{ if (chkCol[y].checked) // Checkbox 'checked'?
{ tmpTotal++; // Yes, so increment 'checked' counter
lastChecked = y;
}
}
if (tmpTotal>3) // A fourth item was checked
{ alert('You cannot select more than three toppings.');
chkCol[lastChecked].checked = 0; // Uncheck fourth checked option
tmpTotal=3;
}
if (tmpTotal == 3) // Exactly three items checked
{ submitElem.style.visibility = "visible"; }
}
// Iterate through collection of INPUT tags. If tag is type 'checkbox'
// the add "onClick" event handler. We provide code for 'attachEvent'
// and 'addEventListner' to handle both IE and non-IE browsers.
for (x=0; x {
if (chkCol[x].type == 'checkbox')
{ if(chkCol[x].addEventListener) // non-IE
{ chkCol[x].addEventListener('click',chkCount,false); }
else if (chkCol[x].attachEvent) // IE
{ chkCol[x].attachEvent('onclick',chkCount); }
}
}
// Alter CSS to make "Submit Survey" link look more like a button.
// We add each style attribute individually in order to preserve
// any other existing style attributes.
submitElem.style.color="blue";
submitElem.style.fontSize="12pt";
submitElem.style.padding="3px";
submitElem.style.marginBottom="20px";
submitElem.style.border="1px solid blue;
submitElem.style.backgroundColor="#E6F7FD";
// The "Submit Survey" link should be hidden by default
submitElem.style.visibility = "hidden";
< /script >
You're done -- click "UPDATE" to save the code you radded to the FOOTER field.
Now it's time to try things out -- click three items on the survey and verify that the "Submit Survey" link appears upon clicking the third item. Unclick an item to see the "Submit" link go away. Click four items to get the "You can only select three toppings" popup box.
Code Commentary
A couple of items need to be called out and explained:
Attaching Events
You'll notice that we had to provide some cross-browser support when attaching the "onClick" event to our checkboxes. As usual IE has its own implementation of things. You'll notice that the IE implementation requires only two paramaters (for attachEvent) while other browsers pass three paramters (for addEventListener). Also, IE references the "onClick" event as "onclick" while other browsers reference it as "click". Further, IE does pass event element references or bubble events through nested elements the same way other browsers do. This presents a serious limitation which we'll cover below under the section titled "lastChecked".
Suffice to say that, for our cross-browser check, we simply check to see if the browser supports each method before attempting to apply each method. The routine included here takes care of all "modern" browsers.
lastChecked
The purpose of this variable is to keep a handy reference to the final checked item (if any) in our collection of checkboxes. If we find that more than three items are checked, we use this reference to "uncheck" the fourth checked item. Simple!
Note that this does NOT uncheck the most recently-checked item. Rather, it simply un-checks the fourth checked item in the list. For instance, if a user clicked items 2, 3 and 4 and then clicked item 1 our code would un-check item 4.
Why not uncheck the most recently-checked item?
The answer lies in how IE handles event calls. First, IE is passed a copy of the calling element rather than a reference to the calling element. This means that you cannot "uncheck" the calling element. Other browsers allow you to simply use "this" within your JavaScript function as a reference to the calling element.
Why not pass a reference to the element as a parameter?
When writing inline event calls we can easily include a "this" element reference to pass the element reference to the target function. Therefore (even in IE) the function can then use the passed paramter to refrence the element that called it and can take action on that element (i.e., "uncheck" the most-recently-checked checkbox).
Unfortunately, when attaching events dynamically, we must call the function by its name but may not include parameters. Strike two for IE. There IS a solution to this problem, actually -- see the section "Element References and IE" at the end of this post. It's a little more involved so we're skipping it in favor of a simple solution for this post.
Multiple-Question Surveys
This super-simple example only had a single question. What if we had more than one question? For instance, imagine if we added a second question "Pick any toppoings you are allergic to." and then presented the same checkbox list of toppings as possible answers. Our example would be broken since new questions are added to the TOP of the list of survey questions (changing the ID of existing questions). Even worse (in SURVEY version 4.5), subsequent edits of questions cause them to change order (go to the bottom of the list) in the survey. How can we point our "pick 3" function to the correct question?
Fortunately the resulting ID values are predictable -- once your survey is in its "final" form. Looking back at the top of our code we find the declaration of the variable "rootelem":
// Get parent TABLE element for this survey question
var rootElem = document.getElementById('dnn_ctr[your module ID here]_Survey_lstSurvey_ctl00_chkOptions');
Notice the number "00" near the end of the ID value just after "_ct" and just before "_chkOptions". This number corresponds the ordinal value of the survey question. Question #1 will be value "00", question #2 will be value "01", etc. If you have a multiple-question survey, simply alter the "00" to match the number representing your question.
Need to quickly ID an ID? Use a Tool!
Note that the ID value for a "radio button" question has a different form than the ID for a "checkbox" question (tricky!). An easy way to quickly find ID values is to use an appropriate browser tool. With the right tool, you can simply mouse over a page element and get info about it. You may never need to crack open and manually sift through "Page Source" again!
Check out my post and screenshots of one such free tool for IE here:
DebugBar for Internet Explorer
http://www.eguanasolutions.com/DNN_Blog/EntryID/19.aspx
Element References and IE
As promised, here is info on a method to determine a the calling element in IE for dynamically-added event handlers (event handlers that cannot include passed parameters). See the code and a brief explanation here:
addEventListener attachEvent - Pass parameters to event-function
http://www.captain.at/howto-addeventlistener-attachevent-parameters.php
Summay and Other Ways to Add Script to a DotNetNuke Page
There's more than one way to skin a.........website. And there's more than one way to add client-side script to a DNN page. In this post we looked at using the FOOTER tag to add script targeted at its host module.
In a previous post I detailed a method for adding script to a dynamically-created page through the use of a DNN resource file ("Add custom registrations instructions to the DNN user registration page").
We've also covered a way to add HTML/script to a page based on the results of a database query ("Create a Most-Commented Blogs Module"). That example inserted HTML links but we'll use the same method for adding scripts in an upcoming post on auto-forwarding users to a page based on user role and/or usergroup.
There are also several third-party "script-inject" modules that let you insert script on a page. If you decide to go the module route, make sure the module injects the script server-side during page creation. If it doesn't do that then it's probably not much more useful than the Text/HTML module included with DNN.