Dynamic Rating System Control Using JavaScript - Part 1...
Part one of this series will illustrate the emitting of JavaScript within a control that will select/unselect image elements, using DHTML events onmouseover and onmouseout.
JavaScript can be embedded within ASP.NET server controls. This can add a more dynamic feel to a control, and add to the appearance. The following is part one of a series creating a dynamic rating system, where the user can select a ranking level. This article will specifically focus on the mouse over/mouse out portion of the control, rendering JavaScript for the list items.
The newer browsers support more advanced CSS styles and DHTML functionality. Specifically, this control will use the onmouseover and onmouseout events, which are client-side events that can perform scripting functionality when the mouse moves over an HTML tag and then subsequently out of it.
JavaScript is added to the control through the Page variable. This variable is defined for System.Web.UI.Control, and is inherited within the class. The System.Web.UI.Page class supports methods named RegisterClientScriptBlock and RegisterStartupScript, which add JavaScript dynamically to a page. The latter method will be used in this article.
The control is a web control that has two image properties, ImageUrl and SelectedImageUrl, which contain the paths to the images used for the mouse over/mouse out. These images defined are used for the <img> elements rendered and will be modified through JavaScript. The following properties are defined below. The values are retained in ViewState, and an ImageUrlEditor is linked to the properties. When editing the properties in the property grid, a special image editor can be used.
|
[ Bindable(true), Category("Appearance"), Editor(typeof(ImageUrlEditor), typeof(System.Drawing.Design.UITypeEditor)) ] public string ImageUrl { get { object o = ViewState["ImageUrl"]; return (o == null) ? string.Empty : o.ToString(); } set { ViewState["ImageUrl"] = value; } } [ Bindable(true), Category("Appearance"), Editor(typeof(ImageUrlEditor), typeof(System.Drawing.Design.UITypeEditor)) ] public string SelectedImageUrl { get { object o = ViewState["SelectedImageUrl"]; return (o == null) ? string.Empty : o.ToString(); } set { ViewState["SelectedImageUrl"] = value; } } |
Even though two images are defined, the control renders N number of images based on the MinimumRange and MaximumRange values, defined below. The RenderContents method has a for loop, looping through the range. The minimum range value must always be smaller than the maximum range.
|
[ Bindable(true), Category("Behavior"), DefaultValue(10) ] public int MaximumRange { get { object o = ViewState["MaximumRange"]; return (o == null) ? 10 : (int)o; } set { if (value <= this.MinimumRange) throw new InvalidOperationException(); ViewState["MaximumRange"] = value; } } [ Bindable(true), Category("Behavior"), DefaultValue(1) ] public int MinimumRange { get { object o = ViewState["MinimumRange"]; return (o == null) ? 1 : (int)o; } set { if (value > this.MaximumRange) throw new InvalidOperationException(); ViewState["MinimumRange"] = value; } } |
To determine the JavaScript behavior, the RangeSelect property stores a Boolean value, stating whether all of the items to the left of the currently selected item will be highlighted, or only the current one will be highlighted. This affects the JavaScript rendered, as well as the logic within.
|
[ Bindable(true), Category("Behavior"), DefaultValue(true) ] public bool RangeSelect { get { object o = ViewState["RangeSelect"]; return (o == null) ? true : (bool)o; } set { ViewState["RangeSelect"] = value; } } |
As you can see below, the control is not a composite control; rather, the RenderContents method is overridden to render each link by writing the HTML instead. This is better performance-wise. Each link is numbered by an incremental value, the value of the for variable “i”. This incremental value is used to uniquely identify the value, in the code-behind and in the JavaScript code rendered. RenderJavaScript receives this incremental value to create two methods per item; one to highlight the item, and one to return the item to its original state. This occurs in the onmouseover and onmouseout client-side events. Note that for these methods, there isn't any server-side event that maps to these client-side events.
|
protected override void RenderContents(HtmlTextWriter writer) { for (int i = this.MinimumRange; i <= this.MaximumRange; i++) { //Render the image writer.AddAttribute(HtmlTextWriterAttribute.Id, this.RatingSystemClientID + i.ToString()); writer.AddAttribute(HtmlTextWriterAttribute.Src, Page.ResolveUrl(this.ImageUrl)); //Render the javascript attributes writer.AddAttribute("onmouseover", "SelectItem" + i.ToString() + "()"); writer.AddAttribute("onmouseout", "HideItem" + i.ToString() + "()"); writer.AddAttribute(HtmlTextWriterAttribute.Border, "0px"); writer.RenderBeginTag(HtmlTextWriterTag.Img); writer.RenderEndTag(); this.RenderJavaScript(i); } |
Note that the JavaScript functions will be noted as SelectItemN and HideItemN, because each method has N number of methods created, one of each per item. The image above defaults to the ImageUrl property for the "src" attribute. When selected, the SelectItemN method is called, which sets the image "src" attribute to the SelectedImageUrl property. Upon moving the mouse out of the image, this attribute is returned back to the value of the ImageUrl property, through the use of HideItemN.
If RangeSelect is true, then the JavaScript emitted will select/unselect the current item, plus each item to the left of it. This is noted through the MinimumRange property. For example, if the range is from one to five, and the fourth item is selected (in actuality, it is moused over, but the term "selected" will be used), the SelectItem1, SelectItem2, SelectItem3, and SelectItem4 method is called through JavaScript code. Conversely, each HideItem method is called when the unselect (“mouse out”) occurs for the fourth item. If RangeSelect is false, then only the current item is selected/unselected. At the very end, the scripts are registered within the page through RegisterStartupScript.
|
protected void RenderJavaScript(int index) { string selScript = string.Empty; string hideScript = string.Empty; if (this.RangeSelect) { selScript += "function SelectItem" + index.ToString() + "() {"; for (int i = this.MinimumRange; i <= index; i++) { selScript += "document.getElementById('" + this.RatingSystemClientID + i.ToString() + "').src = '" + Page.ResolveUrl(this.SelectedImageUrl) + "';"; } selScript += "}"; hideScript += "function HideItem" + index.ToString() + "() {"; for (int i = this.MinimumRange; i <= index; i++) { hideScript += "document.getElementById('" + this.RatingSystemClientID + i.ToString() + "').src = '" + Page.ResolveUrl(this.ImageUrl) + "';"; } hideScript += "}"; } else { selScript += @" function SelectItem" + index.ToString() + @"() { document.getElementById('" + this.RatingSystemClientID + index.ToString() + "').src = '" + Page.ResolveUrl(this.SelectedImageUrl) + @"'; }"; hideScript += @" function HideItem" + index.ToString() + @"() { document.getElementById('" + this.RatingSystemClientID + index.ToString() + "').src = '" + Page.ResolveUrl(this.ImageUrl) + @"'; }"; } if (selScript != string.Empty) { Page.RegisterStartupScript("selscript" + index.ToString(), "<script language='javascript'>" + selScript + "</script>"); } if (hideScript != string.Empty) { Page.RegisterStartupScript("hidescript" + index.ToString(), "<script language='javascript'>" + hideScript + "</script>"); } } |
Viewing the source will show all of the methods that are rendered for the control. There will be one SelectItem and HideItem method per image, and each will have its own <script> container.
The next part in this series will continue building on this control, adding additional features, such as editors, designers, additional UI features, etc. Each will illustrate additional features that can be added to a control to make it more usable for the developer.
You may download the code here.