Section 9.4. Working on the Client Side


9.4. Working on the Client Side

The Sum web part performs its calculations on the server, but that's not really necessary or efficient. For high-volume applications, it's a good idea to do as much work on the client side as possible. For example, the following HTML creates an equivalent client-side web part that doesn't require a roundtrip to the server to perform the calculations:

 <html>          <head>               <script id="clientEventHandlersJS" language="javascript">        function _btn_onclick() {           var arr = new Array("");           // Use getElementById, direct control refs don't work           var _txt = document.getElementById("_txt");           var _div = document.getElementById("_div");           var total = 0, s = _txt.value;           arr = s.split("\n");           for (var i in arr)           {                total += parseFloat(arr[i]);           }           _div.innerText = "Total: " + total;           return;         }                  </script>              </head>            <body>                <form id="_frm">             <div id="_div">Total:             </div>             <TEXTAREA id="_txt" name="_txt" rows="10" cols="35">             </TEXTAREA>             <br>             <INPUT id="_btn" type="button" value="Sum"                  onclick="return _btn_onclick()">        </form>      </body>    </html> 

This code is stored as an HTM file in a resource, then loaded and rendered by the following line:

 ' See "Creating Web Part Appearance" for GetHtml() code.      output.Write(GetHtml("clientSum.htm") 

At runtime this web part is visually identical to the server-side web part in Figure 9-8, but the calculation is done on the client computer via JavaScript. The result is much better performance and less network traffic, since the page isn't sent back to the server every time the member clicks the Sum button.

9.4.1. Using Scripts with Web Controls

The ClientSum web part is efficient, but you can't easily get values from the contained HTML controls once the page returns to the server. To see this limitation, enter some values in ClientSum and refresh with F5: the values you entered are cleared. That happens because the HTML controls don't automatically preserve their state the way that ASP.NET web controls do.

To solve this problem, use ASP.NET web controls rather than HTML controls. In other words, create a hybrid web part that uses both server-side controls and client-side scripts. Using client-side scripts with web controls requires these special steps:

  1. Choose your web control type carefully ; not all web controls are easy to access from client-side code. For example, I had to change a label control to a read-only textbox in order to get its value both on the client side and server side.

  2. Add an ID property for each web control. This property allows you to get a reference to the control from client-side scripts through the getElementsByName method.

  3. Add the web controls to the Controls collection. This step ensures that the controls preserve their state.

  4. Write code to get the web part's UniqueID and pass that value to scripts.

  5. Write client-side scripts that combine the passed-in UniqueID with the element IDs created in step 2.

When SharePoint renders a web part, it includes a lot of special code to preserve the state of web controls in the Controls collection. To make sure that code gets the right values, SharePoint pre-appends a unique identifier to each name and id element in the generated HTML, as shown here:

 <textarea name="FullPage:g_03d3b969_e9c0_4846_9cf5_b14b5e7f6aa7:_txt"      id="FullPage_g_03d3b969_e9c0_4846_9cf5_b14b5e7f6aa7_  _txt"      title="Enter a series of numbers to add." style="height:150px;width:300px;">      </textarea> 

If you don't add an ID property to the control (step 2), SharePoint generates a name attribute and omits id . If you don't add the web controls to the Controls collection (step 3), SharePoint doesn't preserve the state of the control and consequently doesn't pre-append UniqueID . The following code shows these steps implemented for the Sum control:

 ' .NET code running on the server.      ' Declare child controls.      Dim _txt As New TextBox      Dim _br1 As New Literal      Dim _br2 As New Literal  ' Added literal      Dim WithEvents _btn As New Button  Dim _lbl As New TextBox  ' 1) Changed to textbox  Protected Overrides Sub CreateChildControls()         ' Create utility object for dimensions.         Dim u As Unit         ' Set child control properties.         With _txt  .ID = "_txt" ' 2) Added ID for scripts  .            .Width = u.Pixel(300)            .Height = u.Pixel(150)            .TextMode = TextBoxMode.MultiLine            .ToolTip = "Enter a series of numbers to add."          End With          _br1.Text = "<br>"          _br2.Text = "<br>"          _btn.Text = "Postback"   ' Changed.          With _lbl  .ID = "_lbl" ' 2) Added ID for scripts  .             .Text = "Total: "             .ReadOnly = True             .BorderStyle = BorderStyle.None           End With  ' 3) Add the controls in the order to display them  Controls.Add(_lbl)           Controls.Add(_br1)           Controls.Add(_txt)           Controls.Add(_br2)        End Sub        ' Display web part.        Protected Overrides Sub RenderWebPart _          (ByVal output As System.Web.UI.HtmlTextWriter)            ' Load client-side script.            output.Write(GetHtml("sumFinal.js"))            ' Write controls to output stream.            RenderChildren(output)  ' 4) Add button to run script.            output.Write("<INPUT id='_btn' type='button' " & _              "value='Sum' onclick='return _btn_onclick(""" & _              Me.UniqueID() & """)' name='_btn'>")  End Sub 

Step 4 above is a little tricky. I embedded the HTML button control in my code (despite telling you to avoid this) because it's only one control and because it makes it easier to include Me.UniqueID as an argument to _btn_onclick . SharePoint also provides a ReplaceTokens method to replace embedded tokens with SharePoint values. For example, it replaces _WPID_ with the web part's unique ID:

 ' Alternate approach -- use ReplaceTokens      output.Write(  ReplaceTokens  ("<INPUT id='_btn' type='button' value='Sum'" & _        " onclick='return _btn_onclick(""FullPage:  _WPID_  "")' name='_btn'>")) 

ReplaceTokens can replace the following literals. (See the SharePoint SDK for additional details.)

Token

Replacement value

_WPR_

ClassResourcePath property

_WPQ_

Qualifier property

_LogonUser_

Request.ServerVariables ( "LOGON_USER" )

_WPID_

ID property (Control.ID)


The client-side script uses the passed-in UniqueID to get references to web controls through getElementsByName , as shown here:

 // JavaScript running on the client (sumFinal.js).     <script id="clientEventHandlersJS" language="javascript">     // Pass in unique page ID generated by SharePoint.     function _btn_onclick(uID) {         var arr = new Array("");         // Get elements using passed-in unique ID.  var _txt = document.getElementsByName(uID + ":_txt")[0];         var _lbl = document.getElementsByName(uID + ":_lbl")[0];  var total = 0, s = _txt.value;         arr = s.split("\n");         for (var i in arr)             total += parseFloat(arr[i]);         _lbl.innerText = "Total: " + total;         return;      }      </script> 

This completed control will now perform its calculation on the client side, and preserve its settings. Also, the values of the _lbl and _txt web controls are now available to the server. To test, add this code, set a breakpoint, and click the Postback button to see the values displayed in the Visual Studio Debug window:

 Private Sub _btn_Click(ByVal sender As Object, _        ByVal e As System.EventArgs) Handles _btn.Click          ' Display values from controls          Debug.Write(_txt.Text)          Debug.WriteLine(_lbl.Text)      End Sub 

Figure 9-9 shows the completed control at runtime in Debug mode.

animal 9-9. Using debug mode to make sure control values are available on the server

9.4.2. Importing Script Blocks

I explained how to store HTML as a resource because it is a better way to build tables and other display elements of a web part. And I just used that same technique to insert a client-side script into a web part. That's OK for small scripts, but it means you have to rebuild the assembly each time you change the script.

You can import externally stored scripts directly into a web part using the RegisterClientScriptBlock method. That method imports scripts from a server folder at runtime so you can modify and debug scripts without rebuilding the assembly. Instead, you can simply refresh the page (F5) to get the changes.

To import script blocks into a web part:

  1. Create a subfolder in the server's wpresources or _wpresources virtual folder for the scripts.

  2. Copy the script to the new folder.

  3. Add code to the web part's PreRender event to load the script from the new location.

The physical locations shown here are the defaults used when IIS and SharePoint are installed. Your locations may be different. Check the wpresources or _wpresources virtual folders in IIS for your actual paths.


Where you store the scripts depends on how the web part is installed. For web parts installed in a \ bin folder, create a subfolder named after the web part assembly in the C:\InetPub\ wwwroot \wpresouces folder. For example:

 C:\InetPub\wwwroot\wpresouces\Ch09Samples 

For web parts installed in the global assembly cache (GAC), create the assembly folder in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\wpresources ; then create a sub folder in the new folder using the Version , Culture , and PublickKeyToken attributes from the web part's SafeControl element in Web.config . The name has the following form:

  version_culture_token  

Omit culture if the assembly culture is neutral. For example:

 C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\wpresources\       Ch09Samples\ 1.0.0.0_  _fb6919fe58e4ba63 

When you copy the script files to the new folder, remember to remove surrounding <script> tags from the file or you will get syntax errors when you import the script. Scripts should only be loaded once per page, so you need to create a unique key for each script file. Then determine whether the file has already been imported by checking IsClientScriptBlockRegistered before calling RegisterClientScriptBlock , as shown here:

 Private Sub ImportScripts_PreRender(ByVal sender As Object, _         ByVal e As System.EventArgs) Handles MyBase.PreRender           Const sKey As String = "sumFinal.js"           Dim sFile As String = Me.ClassResourcePath & "/sumFinal.js"           Dim sBlock As String = "<script language='javascript' src='" & sFile & "'/>"           ' Load client-side script.           If (Not Me.Page.IsClientScriptBlockRegistered(sKey)) Then _           Me.Page.RegisterClientScriptBlock(sKey, sBlock)       End Sub 

In the preceding code, ClassResourcePath returns the location of the folder you created for script storage. The RegisterClientScriptBlock method inserts the script tag sBlock on the page and registers the script as sKey so it won't be loaded again. At runtime, SharePoint renders this output:

 <script language='javascript'      src='http://localhost/wpresources/Ch09Samples/sumFinal.js'/> 



Essential SharePoint
Essential SharePoint 2007: A Practical Guide for Users, Administrators and Developers
ISBN: 0596514077
EAN: 2147483647
Year: 2005
Pages: 153
Authors: Jeff Webb

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net