Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
106
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

998 C H A P T E R 2 9 J AVA S C R I P T

To ensure that the component is as reusable as possible, it provides properties such as Scrollbars, Height, Width, Resizable, Pop, and Url, which allow you to configure the generated JavaScript. Here are the PopUp properties:

public bool PopUnder

{

get {return (bool)ViewState["PopUnder"];} set {ViewState["PopUnder"] = value;}

}

public string Url

{

get {return (string)ViewState["Url"];} set {ViewState["Url"] = value;}

}

public int WindowHeight

{

get {return (int)ViewState["WindowHeight"];} set

{

if (value < 1)

throw new ArgumentException("WindowHeight must be greater than 0"); ViewState["WindowHeight"] = value;

}

}

public int WindowWidth

{

get {return (int)ViewState["WindowWidth"];} set

{

if (value < 1)

throw new ArgumentException("WindowWidth must be greater than 0"); ViewState["WindowWidth"] = value;

}

}

public bool Resizable

{

get {return (bool)ViewState["Resizable"];} set {ViewState["Resizable"] = value;}

}

public bool Scrollbars

{

get {return (bool)ViewState["Scrollbars"];} set {ViewState["Scrollbars"] = value;}

}

Now that the control has defined these properties, it’s time to put them to work in the Render() method, which writes the JavaScript code to the page. The first step is to make sure the browser supports JavaScript. You can examine the Page.Request.Browser.JavaScript property, which returns true or false, but this approach is considered obsolete (because it doesn’t give you the flexibility to

C H A P T E R 2 9 J AVA S C R I P T

999

distinguish between different levels of JavaScript support). The recommended solution is to check that the Page.Request.Browser.EcmaScriptVersion is greater than or equal to 1, which implies JavaScript support.

If JavaScript is supported, the code uses a StringBuilder to build the script block. This code is fairly straightforward—the only unusual detail is that the Boolean Scrollbars and Resizable values need to be converted to integers and then to strings. This means the required form is scrollbars=1 rather than scrollbars=true (which is the text you end up with if you convert a Boolean value directly to a string).

Here’s the complete rendering code:

protected override void Render(HtmlTextWriter writer)

{

if (Page.Request.Browser.EcmaScriptVersion.Major >= 1)

{

StringBuilder javaScriptString = new StringBuilder(); javaScriptString.Append("<script language='JavaScript'>"); javaScriptString.Append("\n<!-- "); javaScriptString.Append("\nwindow.open('"); javaScriptString.Append(Url + "', '" + ID); javaScriptString.Append("','toolbar=0,"); javaScriptString.Append("height=" + (WindowHeight + ",")); javaScriptString.Append("width=" + (WindowWidth + ",")); javaScriptString.Append("resizable=" +

Convert.ToInt16(Resizable).ToString() + ","); javaScriptString.Append("scrollbars=" +

Convert.ToInt16(Scrollbars).ToString()); javaScriptString.Append("');\n");

if (PopUnder) javaScriptString.Append("window.focus();"); javaScriptString.Append("\n-->\n"); javaScriptString.Append("</script>\n"); writer.Write(javaScriptString.ToString());

}

else

{

writer.Write( "<!-- This browser does not support JavaScript -->");

}

}

Figure 29-13 shows the PopUp control in action.

Tip Usually, custom controls register JavaScript blocks in the OnPreRender() method, rather than writing it directly in the Render() method. However, the PopUp control bypasses this approach and takes direct control of writing the script block. That’s because you don’t want the usual behavior of one script block for multiple PopUp controls. Instead, if the developer adds more than one PopUp control, you want to create a separate script block for each control. This gives the developer the ability to create pages that display multiple pop-up windows.

If you want to enhance the PopUp component, you can add more properties. For example, you could add properties that allow you to specify the position where the window will be displayed. Some websites use advertisements that don’t appear for several seconds. You could use this technique with this component by adding a JavaScript timer (and wrapping it with a control property that allows you to specify the number of seconds to wait). Once again, the basic idea is to give the

page developer a neat object to program with and the ability to use the rendering methods to generate the required JavaScript in the page.

1000 C H A P T E R 2 9 J AVA S C R I P T

Figure 29-13. Showing a pop-up window

Rollover Buttons

Rollover buttons are another useful JavaScript trick that has no equivalent in the ASP.NET world. A rollover button displays one image when it first appears and another image when the mouse hovers over it (and sometimes a third image when the image is clicked).

To provide the rollover effect, a rollover button usually consists of an <img> tag that handles the onClick, onMouseOver, and onMouseOut JavaScript events. These events will call a function that swaps images for the current button, like this:

<script language='JavaScript'> function swapImg(id, url) {

elm = document.getElementById(id); if(elm) elm.src=url;

}

</script>

A configured <img> tag would then look like this:

<img src="buttonOriginal.jpg" onMouseOver="swapImg('RollOverButton1', 'buttonMouseOver.jpg');" onMouseOut="swapImg('RollOverButton1', 'buttonOriginal.jpg');" />

Rollover buttons are a mainstay on the Web, and it’s fairly easy to fill the gap in ASP.NET with a custom control. The easiest way to create this control is to derive from the WebControl class and use <img> as the base tag. You also need to implement the IPostBackEventHandler to allow the button to trigger a server-side event when clicked.

Here’s the declaration for the RollOverButton and its constructor:

public class RollOverButton : WebControl, IPostBackEventHandler

{

public RollOverButton() : base(HtmlTextWriterTag.Img) { ... }

// Other code omitted.

}

C H A P T E R 2 9 J AVA S C R I P T

1001

The RollOverButton class provides two properties—one URL for the original image and another URL for the image that should be shown when the user moves the mouse over the button. Here are the property definitions:

public string ImageUrl

{

get {return (string)ViewState["ImageUrl"];} set {ViewState["ImageUrl"] = value;}

}

public string MouseOverImageUrl

{

get {return (string)ViewState["MouseOverImageUrl"];} set {ViewState["MouseOverImageUrl"] = value;}

}

The next step is to have the control emit the client-side JavaScript that can swap between the two pictures. In this case, it’s quite likely that there will be multiple RollOverButton instances on the same page. That means you need to register the script block with a control-specific key so that no matter how many buttons you add there’s only a single instance of the function. By convention, this script block is registered by overriding the OnPreRender() method, which is called just before the rendering process starts, as shown here:

protected override void OnPreRender(EventArgs e)

{

if (!Page.ClientScript.IsClientScriptBlockRegistered("swapImg"))

{

string script =

"<script language='JavaScript'> " + "function swapImg(id, url) { " +

"elm = document.getElementById(id); " + "if(elm) elm.src=url; }" +

"</script> "; Page.ClientScript.RegisterClientScriptBlock(this.GetType(),

"swapImg", script);

}

base.OnPreRender (e);

}

This code explicitly checks whether the script block has been registered using the IsClientScriptBlockRegistered() method. You don’t actually need to test this property; as long as you use the same key, ASP.NET will render only a single instance of the script block. However, you can use the IsClientScriptBlockRegistered() and IsStartupScriptRegistered() methods to avoid performing potentially time-consuming work. In this example, it saves the minor overhead of constructing

the script block string if you don’t need it.

Tip To really streamline your custom control code, put all your JavaScript code into a separate file, embed that file into your compiled control assembly, and then expose it through a URL using the WebResource attribute, as discussed in Chapter 28. This is the approach that ASP.NET uses with its validation controls, for example.

Remember that because RollOverButton derives from WebControl and uses <img> as the base tag, it already has the rendering smarts to output an <img> tag. The only parts you need to supply are the attributes, such as name and src. Additionally, you need to handle the onClick event (to post

1002 C H A P T E R 2 9 J AVA S C R I P T

back the page) and the onMouseOver and onMouseOut events to swap the image. You can do this by overriding the AddAttributesToRender() method, as follows:

protected override void AddAttributesToRender(HtmlTextWriter output)

{

output.AddAttribute("name", ClientID); output.AddAttribute("src", ImageUrl);

output.AddAttribute("onClick", Page.ClientScript.GetPostBackEventReference( new PostBackOptions(this)));

output.AddAttribute("onMouseOver",

"swapImg('" + this.ClientID + "', '" + MouseOverImageUrl + "');");

output.AddAttribute("onMouseOut",

"swapImg('" + this.ClientID + "', '" + ImageUrl + "');");

}

The last ingredient is to create the RaisePostBackEvent() method, as required by the IPostBackEventHandler interface, and use it to raise a server-side event, as shown here:

public void RaisePostBackEvent(string eventArgument)

{

OnImageClicked(new EventArgs());

}

public event EventHandler ImageClicked;

protected virtual void OnImageClicked(EventArgs e)

{

// Check for at least one listener and then raise the event. if (ImageClicked != null)

ImageClicked(this, e);

}

Figure 29-14 shows a page with two rollover buttons.

Figure 29-14. Using a rollover button

C H A P T E R 2 9 J AVA S C R I P T

1003

Dynamic Panels

Earlier in this chapter, you considered the new ASP.NET 2.0 client callback features. Integrating these features into a page is a lot of work. However, a much better option is to use them to build rich controls. You can then use these controls in as many pages as you want. Best of all, you’ll get the Windows-style responsiveness without having to delve into the lower-level callback infrastructure.

Although there’s no limit to the type of controls you might build with dynamic callbacks, many controls use callbacks to simply refresh a portion of their user interface (such as the TreeView). With a little ingenuity, you can create a container control that provides this functionality for free.

The basic idea is to create a new control that derives from Panel. This panel contains content that you want to refresh. At some point, a client-side JavaScript will occur, causing the panel to perform a callback. At this point, the panel will fire a server-side event to notify your code. You can handle this event and tweak any of the controls inside the panel. When the event finishes, the panel gets the new HTML for its contents and returns it. A client-side script replaces the current panel contents with the new HTML using a little DHTML.

Figure 29-15 shows the process.

Figure 29-15. Refreshing a portion of the page through a callback

The DynamicPanel

The first step is to derive a class from Panel and implement ICallbackEventHandler:

public class DynamicPanel : Panel, ICallbackEventHandler { ... }

As part of the ICallbackEventHandler, the DynamicPanel needs to implement the RaiseCallbackEvent() and GetCallbackResult() method.. At this point it’s a two-step process. First, the DynamicPanel needs to fire an event to notify your page. Your page can handle this event and perform the appropriate modifications. Next, the DynamicPanel needs to render the HTML for its contents. It can then return that information (along with its client ID) to the client-side web page.

public event EventHandler Refresh;

public void RaiseCallbackEvent(string eventArgument)

{

// Fire an event to notify the client a refresh has been requested. if (Refresh != null)

{

1004 C H A P T E R 2 9 J AVA S C R I P T

Refresh(this, EventArgs.Empty);

}

}

}

public string GetCallbackResult()

{

// Prepare the text response that will be sent back to the page. EnsureChildControls();

using (StringWriter w = new StringWriter())

{

using (HtmlTextWriter writer = new HtmlTextWriter(sw))

{

// Add the id that identifies this panel. writer.Write(this.ClientID + "_");

// Render just the part of the page inside the panel. this.RenderContents(writer);

}

return w.ToString();

}

}

If you’ve programmed DHTML scripts before, you know that all you need to manipulate an HTML element is its unique ID and the getElementById() method. Here’s the client-side script code that finds the panel on the page and then replaces its content with new HTML:

<script language='JavaScript'>

function RefreshPanel(result, context)

{

if (result != '')

{

//Split the string back into two pieces of information:

//the panel ID and the HTML content.

var separator = result.indexOf('_');

var elementName = result.substr(0, separator); // Look up the panel.

var panel = document.getElementById(elementName); // Replace its content.

panel.innerHTML = result.substr(separator+1);

}

}

</script>

Rather than hard-code this script into every page in which you use the panel, it makes sense to register it programmatically in the DynamicPanel.OnInit() method:

protected override void OnInit(EventArgs e)

{

base.OnInit(e); string script = "..."

Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "RefreshPanel", script);

}

This completes the basics of the DynamicPanel. However, this example still has a significant limitation—the page has no way to trigger the callback and cause the panel to refresh. That means it’s up to your page code to retrieve the callback reference and insert it into your page.

C H A P T E R 2 9 J AVA S C R I P T

1005

Fortunately, you can simplify this process by creating other controls that work with the DynamicPanel. For example, you could create a DynamicPanelRefreshLink that, when clicked, automatically triggers a refresh in the associated panel.

The first step in implementing this solution is to revisit the DynamicPanel and implement the ICallbackContainer interface.

public class DynamicPanel : Panel, ICallbackEventHandler, ICallbackContainer { ... }

This interface allows the DynamicPanel to provide the callback reference, rather than forcing you to go through the page.

To implement ICallbackContainer, you need to provide a GetCallbackScript() method that returns the reference. Here the Panel can rely on the page, making sure to specify itself as the callback target, and on RefreshPanel() as the client-side script that will handle the response.

public string GetCallbackScript(IButtonControl buttonControl, string argument)

{

return Page.ClientScript.GetCallbackEventReference( this, "", "RefreshPanel", "null");

}

The DynamicPanelRefreshLink

Now you’re ready to implement the much simpler refresh button. This control, named DynamicPanelRefreshLink, derives from LinkButton.

public class DynamicPanelRefreshLink : LinkButton { ... }

You specify the panel that it should work with by setting a PanelID property:

public string PanelID

{

get { return (string)ViewState["DynamicPanelID"]; } set { ViewState["DynamicPanelID"] = value; }

}

Finally, when it’s time to render itself, the DynamicPanelRefreshLink finds the associated DynamicPanel control using FindControl() and then adds the callback script reference to the onclick attribute.

protected override void AddAttributesToRender(HtmlTextWriter writer)

{

DynamicPanel pnl = (DynamicPanel)Page.FindControl(PanelID); if (pnl != null)

{

writer.AddAttribute("onclick", pnl.GetCallbackScript(this, ""));

}

}

The Client Page

To complete this example, create a simple text page, and add a DynamicPanel and a DynamicPanelRefreshLink underneath it. Set the DynamicPanelRefreshLink.PanelID property to create the link.

Next, place some content and controls in the panel. Finally, add an event handler for the DynamicPanel.Refresh event and use it to change the content or formatting of the controls in the panel.

1006 C H A P T E R 2 9 J AVA S C R I P T

protected void Panel1_Refres(object sender, EventArgs e)

{

Label1.Text = "This was refreshed without a postback at " + DateTime.Now.ToString();

}

Now when you run the page, you’ll see that you can click the DynamicPanelRefreshLink to refresh the panel without posting back the page (see Figure 29-16).

Figure 29-16. The DynamicPanel

Note This example is extremely practical. However, before you start using the DynamicPanel in your applications, you might want to consider a more mature sample by Bertrand Le Roy that uses the same technique but adds a fair bit of extra frills (and a lot more code). To check it out, surf to http://www.gotdotnet.com/ workspaces/directory.aspx, and search for RefreshPanel.

Frames

Another well-established feature of the Web is frames. Frames allow you to display more than one HTML document in the same browser window. Frames are commonly used to provide navigational controls (such as a menu with links) that remain visible on every page. You could simulate the same effect by creating a user control for navigation and including it on every page. However, only by using frames can you ensure that the placement is exactly the same. Frames also give you the ability to independently scroll the content frame while keeping the navigational controls fixed in place.

Tip For more information about frames, refer to the tutorial http://www.w3schools.com/html/_html_ frames.asp or the FAQ at http://www.htmlhelp.com/faq/html/frames.html. Frames, like JavaScript, are completely independent of ASP.NET. They are simply a part of the HTML standard.

C H A P T E R 2 9 J AVA S C R I P T

1007

Unfortunately, frames aren’t always that easy to integrate into an ASP.NET page. Showing separate frames is easy—you simply need to create an HTML frames page that references the ASP.NET pages you want to show and defines their positioning. However, developers often want an action in one frame to have a result in another frame, and this interaction is not as straightforward. The problem is that each frame loads a different page, and from the point of view of the web server these pages are completely separate. That means the only way one frame can interact with another is through the browser, using client-side script.

Frame Navigation

When you use frames for navigation, the user needs to be able to click a link in one frame and load a new page in the other frame. You can more easily accomplish this task on the client than on the server.

For example, consider the following HTML page, which defines a frameset with two frames (a navigation frame on the left and a content frame on the right):

<html>

<head>

<title>Frame Test</title> </head>

<frameset framespacing="1" cols="200,*">

<frame name="menu" src="Frame1.aspx" scrolling="no" /> <frame name="content" src="" scrolling="auto" /> <noframes>

<body>

<p>This page uses frames, but your browser doesn't support them.</p> </body>

</noframes>

</frameset>

</html>

The left frame shows the Frame1.aspx page. In this page, you might want to add controls that set the content in the other frame. This is easy to do using static HTML, such as an anchor tag. For example, if a user clicks the following hyperlink, it will automatically load the target NewPage.aspx in the frame on the right, which is named content:

<a href="NewPage.aspx" target="content">Click here</a>

You can also perform the same feat when a JavaScript event occurs by setting the parent. [FrameName].location property. For example, you could add an <img> tag on the left frame and use it to set the content on the right frame, as shown here:

<img src="ImgFile.gif" onClick="parent.content.location='NewPage.aspx'">

However, navigation becomes more complicated if you want to perform programmatic frame navigation in response to a server-side event. For example, you might want to log the user’s action, examine security credentials, or commit data to a database and then perform the frame navigation. The only way to accomplish frame navigation from the server side is to write a snippet of JavaScript that instructs the browser to change the location of the other frame when the page first loads on the client.

For example, imagine you add a button to the leftmost frame, as shown in Figure 29-17. When this button is clicked, the following server-side code runs. It defines the <script> block and then registers it in the page. When the page is posted back, the script executes and redirects the rightmost frame to the requested page.