Tuesday, March 9, 2010

How To: Create A Scrolling News / Information Marquee using JavaScript and Server Side Code

Have you ever wanted to add a continuous news scroller at the bottom of your web page, that was dynamically populated from database data at runtime? This will show you all of the pieces needed to create the scroller and update it server side in code behind. The main parts are the news scroller itself, built using JavaScript, and the content that populates the scroller which is done server side in VB.NET. I will say while this is not overly complex, it does have several parts and pieces that go together, so beginners may get a bit confused. If this is the case, just take your time and try this on a new blank page before attempting to inject this into existing logic. Also remember that all JavaScript is case sensitive, so be careful when making calls to the functions.

Let's get right into the code. I will give credit to dhtmlgoodies where I was able to get some of the initial concepts for scrolling. I have since greatly expanded on the basics by populating the contents server side, impoving the scrolling visually, and an AJAX asynchronous postback behind the scenes each time the scroller has moved across the page to get updated data. I will leave out the AJAX portion of this because it is an entire post in itself, and we will just get the server side populated scroller working 1st. To being we are going to create a placeholder 'DIV' tag on the page. The 'innerHTML' code will be created and set server side. This is just the container:














Notice above, the call to initialize the marquee. This is a call to a JavaScript method which will be explained a little later on. For now we need to make sure that call is made, so that the scrolling effect will begin when the page has completed loading. Within this call I sent it a page defined variable value, which is exposed server side. You might ask, why do it this way and not just declare inline document.getElementById...etc. Well I often use ASP.NET Master and Content pages, and the control naming structure for content pages is a bit more complex than calling a control directly. In this situation exposing the value server side is a much easier alternative to hardcoding its value. If you are using a traditional page, you can probably streamline this method and extract the control's ID directly.

Next, here is the .css class that was defined in the 'DIV' element above and the class defining the style of the text that will be inside of that DIV. You can modify this as needed, however the classes must exist as named or the JavaScript will not work properly.


.ScrollingMarqueeStyle
{
padding-top:3px;
height:25px;
background-color: #000;
position:absolute;
left:0px;
z-index:1000;
bottom:0px;
display:none;
width:100%;
overflow:hidden;

/*Border that hovers over the scrolling marquee*/
border-top: solid 2px red;
}
body > div.ScrollingMarqueeStyle
{
/* Firefox */
position:fixed;
}
.ScrollingMarqueeStyle .MarqueeTextObj
{
/* Text within marquee */
position:absolute;
color: #FFF;
font-weight:bold;
white-space:nowrap;
font-family: Trebuchet MS;
font-size: 16px; /*This is the size of the scrolling text*/
}
/* Applies to custom classes used in <Span&rt; tags within the scrolling text */
.DescriptionText
{
color:#66FF33;
}
Now let's take a look at the JavaScript that works the magic behind the scrolling marquee. There are (5) functions: InitHorizontalScrollMarquee, MoveScrollingMarquee, PositionScrollingMarquee, Stop_MoveScrollingMarquee, and Resume_MoveScrollingMarquee. These can all be placed either within a tag in the head of the page, or within a .js file that is referenced by the page.

// Higher = Faster, Lower = slower and more smoothly (equates to pixels per move on the screen)
var marqueeSteps = 1;
// Lower value = Faster (# equates to Milleseconds - ms between moves. Watch out for CPU usage if too low)
var marqueeSpeed = 25;
// Make the marquee stop moving when user moves his mouse over it, set to 'true' to enable, 'false' to disable.
var marqueeStopOnMouseOver = true;
// "top" or "bottom" position set here
var marqueePosition = 'bottom';

/* Don't change anything below here */
var marqueeObj;
var marqueeTextObj;
var marqueeTmpStep;
var marqueeTextObjects = new Array();
var marqueeHiddenSpans = new Array();
var marqueeIndex = 0;

var hScrollRefreshIntervalId;

function PositionScrollingMarquee(e, timeout) {
if (document.all) e = event;
if (marqueePosition.toLowerCase() == 'top') {
//Place the marquee at the top of the page
marqueeObj.style.top = '0px';
}
else {
//Place the arquee at the bottom of the page
marqueeObj.style.bottom = '-1px';
}
if (document.all && !timeout) setTimeout('PositionScrollingMarquee(false,true)', 500)
}


function MoveScrollingMarquee() {

//Get left-side position of the marquee
var leftPos = marqueeTextObj.offsetLeft;

//Subtract step count set at global level and kept track of in InitMarquee (Move DIV left)
leftPos = leftPos - marqueeTmpStep;

//Left position of DIV + [array value] + width of a single 'marqueeTextObj'
var rightEdge = leftPos + marqueeHiddenSpans[marqueeIndex].offsetLeft + marqueeTextObj.offsetWidth;

//If the right edge calculated above is less than 0, this means the ENTIRE marquee is off
//the left side of the page. At this point reset the scrolling marquee to the right side of the page to re-scroll.
if (rightEdge < 0) {


//Reset the marquee to be just off the right side of the page which is done
//by making its left position the width of the page
leftPos = document.documentElement.offsetWidth;
marqueeTextObj.style.left = leftPos + 'px';

//Set marquee properties
marqueeTextObj.style.display = 'none';

marqueeIndex++;
if (marqueeIndex >= marqueeTextObjects.length) marqueeIndex = 0;
marqueeObj = marqueeTextObjects[marqueeIndex];

marqueeTextObj.style.display = 'block';

}

//Move the DIV to the left position calculated above. This is where the 'magic' happens
//and the 'scrolling' occurs.
marqueeTextObj.style.left = leftPos + 'px';

}

function Stop_MoveScrollingMarquee() {
if (marqueeStopOnMouseOver) marqueeTmpStep = 0;
}


function Resume_MoveScrollingMarquee() {
marqueeTmpStep = marqueeSteps;
}

function InitHorizontalScrollMarquee(MarqueeDivControlID) {

//Get the marquee object properties using the ID passed in from the calling code:
marqueeObj = document.getElementById(MarqueeDivControlID);


var spans = marqueeObj.getElementsByTagName('DIV');
for (var no = 0; no < spans.length; no++) {

//Find the 'MarqueeTextObj' class and set its properties
if (spans[no].className == 'MarqueeTextObj') {
marqueeTextObj = spans[no];
spans[no].style.display = 'block';

marqueeTextObjects.push(spans[no]);
var hiddenSpan = document.createElement('SPAN');
hiddenSpan.innerHTML = ' '
spans[no].appendChild(hiddenSpan);
marqueeHiddenSpans.push(hiddenSpan);
}
}
if (marqueePosition.toLowerCase() == 'top') {
marqueeObj.style.top = '0px';
}
else {
if (document.all) {
marqueeObj.style.bottom = '0px';
}
else {
marqueeObj.style.bottom = '-1px';
}
}

//Set the left position of the marquee = to the width of the page which essentially places
//its leftmost part at the right end of the page.
marqueeTextObj.style.left = document.documentElement.offsetWidth + 'px';

//Define the mouse events; these are wired up to the varibale named 'marqueeStopOnMouseOver' which is false by default.
marqueeObj.onmouseover = Stop_MoveScrollingMarquee;
marqueeObj.onmouseout = Resume_MoveScrollingMarquee;
if (document.all) window.onscroll = PositionScrollingMarquee; else marqueeObj.style.position = 'fixed';

marqueeObj.style.display = 'block';
marqueeTmpStep = marqueeSteps;

//setInterval() returns an interval ID, which later can be passed to clearInterval(): to stop repeat calls if desired;
hScrollRefreshIntervalId = setInterval('MoveScrollingMarquee()', marqueeSpeed);

}

I will touch briefly on the JS code, but I tried to comment up the code well so you can read the documentation within. Basically, 'InitHorizontalScrollMarquee' is called, and finds the DIV we created server side with the class named 'MarqueeTextObj' to set its properties. Look closely at the JS variables defined prior to all of the functions. These dictate a lot of the behavior attributes like position (top or bottom), speed, and the ability to pause scrolling when hovering over with the mouse. Lastly, the 'setInterval' JS function (basically a timer) is wired up to call the 'MoveScrollingMarquee' method on interval. This method when called in turn moves the entire 'DIV' with text to the left by the step amount specified. The end result is a scrolling marquee!

Now, let’s move onto the code behind that will actually populate the marquee. Now the code I will provide simply creates a DataTable and populates it with City names and their respective temperatures. This entry is not going to dive into how to call a database and build up a custom object, list of objects ADO.NET type, entity, etc.; I assume most already know how to do this. So for my example I am going to build up some static data, but certainly you would extract this data dynamically from the database and iterate through it as I do to build out the 'InnerHTML' that will be set on our server side 'DIV' control we added to the page earlier.

This code example also does everything directly behind the page which is also not a great practice and does not follow any n-layer or design pattern guidelines. This code would need to be farmed out to its respective spots within the application, but again that is not the purpose of this entry. So below is the code needed to build the elements that go into the scrolling marquee:



Imports System.IO

Partial Public Class WebForm1
Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

'This is where you would make calls to get a business object, list of objects, DataTable, DataSet, etc.
'Since that is not the purpose of this entry, I will just manually build out a DataTable with (2)
'columns: City and Temperature (of coarse it could be anything, and formatted however needed)
Dim dt As New DataTable()
dt.Columns.Add("City")
dt.Columns.Add("Temperature")

'Now add some dummy values
Dim dr As DataRow
dr = dt.NewRow()
dr("City") = "New York"
dr("Temperature") = "50"
dt.Rows.Add(dr)
dr = dt.NewRow()
dr("City") = "Miami"
dr("Temperature") = "75"
dt.Rows.Add(dr)
dr = dt.NewRow()
dr("City") = "San Diego"
dr("Temperature") = "60"
dt.Rows.Add(dr)
dr = dt.NewRow()
dr("City") = "Chicago"
dr("Temperature") = "45"
dt.Rows.Add(dr)

'Call a local method that will build up the HTML to be assigned to our server sode DIV control named 'ScrollingMarquee':
BuildScrollingMarquee(dt)

End Sub

Private Sub BuildScrollingMarquee(ByVal MarqueeValues As DataTable)

Dim sb As New StringBuilder

'Begin the HTML formatting by starting the DIV tag; the class name must be 'MarqueeTextObj' so it is recognized by JS
sb.Append("
")

'Loop through each datarow in the DataTable passed in, and extract and append the data to the StringBuilder.
'The format for the HTML should be City: [CityName] - Temperature: [Temperature Value]...
For Each dr As DataRow In MarqueeValues.Rows
'Add the string to the StringBuilder with HTML tags
sb.Append("City: " & dr("City").ToString() & " - Temperature: " & dr("Temperature").ToString() & "; ")
Next

sb.Append("
")

'*****Populate the scrolling marquee at the bottom of the page with data in the StrignBuilder formatted above*****
'Set the 'InnerHTML' of the server 'DIV' control
Me.ScrollingMarquee.InnerHtml = sb.ToString.Trim()

End Sub

Protected ReadOnly Property ScrollingMarqueeDivControlID() As String
'This returns the ControlID of the DIV containing the Horizontal scrolling marquee content.
'It will be exposed as a JS varibale in the source behind the page to be used within JavaScript.
Get
Me.EnsureChildControls()
Return Me.ScrollingMarquee.ClientID
End Get
End Property

End Class

The code behind shows that in Page_Load() we build up a DataTable with some data to be placed into the marquee. We then pass that DataTable to a local method that uses a StringBuilder to create properly formatted HTML. The 'BuildScrollingMarquee' method iterates through each DataRow in the passed in DataTable and uses its values to place into the StringBuilder. This is a spot where you can modify formatting, values, colors, you name it, to make the marquee text formatted as you wish. This in conjunction with the .css styles are the (2) main spots that will dictate styles and formatting if you need to make adjustments. Below is a picture of how the marquee looked as I formatted it:
So that's it! You now see how to combine JavaScript code with .NET code to dynamically populate and present a nice horizontal scrolling marquee. One last quick word - before posting back that something isn't working or there are issues, try running this code exactly as is in a new ASP.NET web form before modifying. This code has been tested and works, so take it a step at a time to get this code working before moving forward. As you being to become familiar with the code you can see where the many enhancements could be made to how the data is pulled, when, on what interval, styles, formatting, etc. This code hopefully gets you started with what you need by showing you the framework to dynamically populating a scrolling marquee.

9 comments:

  1. Thanks, It will be nice if you can show how to update the data in a fixed time period

    ReplyDelete
  2. Very nice code -- a question: how would the code be modified to start the scroll at someplace other than at the far right of the screen (if, for instance, you wanted the scroll window to be shorter than the entire length of the screen)?

    ReplyDelete
  3. @sumosumo: The best time to update the data is once the scrolling content has completed scrolling all the way off the left side of the screen for the best visual affect. Otherwise doing this on Timer via a set time, could stop scrolling in the middle and make the visual not too nice. The way to do this is sense in the JS when the scrolling has completed and is about to reset. Then you can raise a serverside event from JS via a '__doPostBack' command that will fetch the updated data before rescrolling the data. I actually do this myself and planned on adding the code as a separte entry. It is a length post that takes time to explain, so I hope to add it in the future.

    @QUL001: Take a look to the 'PositionScrollingMarquee' method and the 'marqueePosition' variable. Currently the logic allows a 'top' or 'bottom' definition for location that could be modified/customized for custom screen positioning quite easy. But you also asked about how far it scrolls to the left. One really easy way is to place the more important object on the screen on top of the marquee at the desired location and make its z-index value higher than the marquee. This would place the new object on top at the specified location and look like the marquee stops scrolling where the new object begins. Then modify the JS in the 'MoveScrollingMarquee()' function. The if (rightEdge < 0) could be changed to size of the new object (i.e. rightEdge < 300), and once the right hand side of the marquee meets the beginning of the object overlaying the marquee, the scrolling would reset.

    ReplyDelete
  4. Let me be more specific with my question – I modified the code so that the marquee is at an absolute position (top 100px, left 100px) rather than at relative top or bottom, and set the width to 600px rather than the full length of the screen to fit the format of my web page. In addition, I then set marqueeTextObj.style.left = '700px' so that the scrolling begins exactly at the right side of the marquee window. The remaining problem is that the scrolling is not present in the window for a few seconds after exiting to the left, because the scrolling starts again at the far right of the screen, outside of my actual marquee window. I am looking for a modification so that the scrolling commences again at the right of my marquee (so the scroll is always visible within the marquee window). Thanks.

    ReplyDelete
  5. Great control !! I am trying to place the scrolling marquee inside a cell in a table. It keeps appearing at the top or bottom of the entire form. I cannot figure out what needs to change in the JS. Any assistance would be greatly appreciated

    ReplyDelete
  6. Hi,
    Am facing a problem with marquee tag. Am getting a browser compatibility issue while binding data in the data list which is in between marquee tags.
    And this issue is only for Google Chrome. So i need ur help...

    Plz do some needful...

    ReplyDelete
  7. can this code be embedded into an user control in C#? I have tried to make a .ascx file and imported into sharepoint but it did not work. not sure why?

    any help?

    ReplyDelete
  8. sb.Append("(<'div' class="marqueeTextObj">)")

    and

    sb.Append("<'span' class="DescriptionText">

    giving me error as "comma, ')', or a valid expression continuation expected.

    Any help appreciated - Suresh (Beginner)

    ReplyDelete