Thursday, April 3, 2008

StateBag Control - Simplifying Data Exchange in Heavy AJAX Pages - Sample Source Code Part 2

You can download code for this article or see it live.

Also you can download source code for StateBag control only.

This is second post with samples in a series of articles about exchanging complex objects between different parts of AJAX enabled page.

You can see the whole article by following these two links:

StateBag Control - Simplifying Data Exchange in Heavy AJAX Pages

StateBag Control - Simplifying Data Exchange in Heavy AJAX Pages - Sample Source Code Part 1

I will remind that I am going to create a sample to demonstrate a power of JSON serialization in AJAX framework, and demonstrate a usage of StateBag control I created in Part 1

Here is the exact picture of what I am trying to do in this post:

image_thumb1

The exact explanation with sample code for "Dynamic Area No 1" can be viewed in the second part, where we created sample using dynamic creation of google charts data in the browser, and then submitting the data to the server.

Today I want to create two remaining areas

Let's start by creating state object:

namespace Devarchive.Net
{
    public class Sample2State
    {
        public string IFrameIO;
        public string ServiceIO;
        public string UpdatePanelIO;
    }
}

Page markup looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="Devarchive.Net" Namespace="Devarchive.Net" TagPrefix="dn" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>StateBag - Sample 2</title>
    <style type="text/css">
        .div
        {
            border:dashed 1px Black;
            float:left; 
            width:200px;
            height:500px;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="SM" runat="server">
            <Services>
                <asp:ServiceReference Path="~/WebService.asmx" />
            </Services>
        </asp:ScriptManager>
        

<dn:StateBag ID="StateBag1" runat="server" StateID="MyState1" />

        <script type="text/javascript">
            var stateBag = null;
            function pageLoad() {
                stateBag = $find("MyState1");
                if(stateBag) {
                    stateBag.add_propertyChanged(stateBagProperyChanged);
                }
            }
            function stateBagProperyChanged(sender, args) {
                if(stateBag && args.get_propertyName()=="state") {
                    outPutCurrentState("property changed in StateBag:");
                }
            }
            function echo() {
                if(stateBag) {
                    // process data from statebag - including parts shared with web service 
                    // and iframe            
                    $get("divOutput").innerHTML = stateBag.get_state().UpdatePanelIO + " - OK.";
                }
            }
            function modify() {
                if(stateBag) {
                    var state = stateBag.get_state();
                    // process data from statebag - including parts shared with web service 
                    // and iframe
                    state.UpdatePanelIO = "Modified from <b>Main Page</b>, time:" + new Date();
                    stateBag.set_state(state);
                    stateBag.raisePropertyChanged('state');
                }
            }
            function outPutCurrentState(additionalMessage) {
                var sb = new Sys.StringBuilder();
                sb.appendLine(additionalMessage+"<br>");
                var state = stateBag.get_state();
                for(var key in state) {
                    sb.appendLine(String.format("{0}: {1}<br>", key, state[key]));
                }
                $get("divOutput").innerHTML = sb.toString();
            }
            
            // service related functions
            
            function echoService() {
                if(stateBag) {
                    WebService.Echo(stateBag.get_state(), echoCallback, errorCallback, null);
                }
            }
            function modifyService() {
                if(stateBag) {
                    WebService.Modify(stateBag.get_state(), modifyCallback, errorCallback, null);
                }
            }
            function echoCallback(result) {
                if(result) {
                    $get("divService").innerHTML = result;
                }
            }
            function modifyCallback(result) {
                if(result) {
                    var state = result;
                    stateBag.set_state(state);
                    stateBag.raisePropertyChanged('state');
                }
            }
            function errorCallback(result) {
                // error handling here
            }
        </script>
        <div>
            <div class="div" runat="server">
                <h4>Echo/Modify state from MainPage</h4>
                <input type="button" id="btnEcho" value="Echo" onclick="echo()" />
                <input type="button" id="btnModify" value="Modify" onclick="modify()" />
                <div  id="divOutput">
                </div>
            </div>
            <div class="div">
                <h4>Echo/Modify state from WebService</h4>
                <input type="button" id="btnEchoService" value="Echo" onclick="echoService()" />
                <input type="button" id="btnModifyService" value="Modify" onclick="modifyService()" />
                <div id="divService">
                </div>
            </div>
            <div class="div">
                <h4>Echo/Modify state from IFrame</h4>
                <iframe scrolling="no" style="border:none; width:190px; height:490px" src="IFrameContents.aspx"></iframe>
            </div>
        </div>
    </form>
</body>
</html>

As you see I created three areas in the page - three divs.

One is modified from javascript, second from iframe, and third using webservice.

You should know that you can simply postback the main form and you will have entire latest state also on the c# code in the code behind of the main page - also if you create one more stateBag control inside iframe and postback the iframe you will get the latest state object also in the code-behind of the iframe. This way we can sync the state even across many pages

 

Now the code of the iframe:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="IFrameContents.aspx.cs" Inherits="IFrameContents" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Inner contents of an iframe</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="SM" />
    <script type="text/javascript">
        var stateBag = null;
        function pageLoad() {
            if(window.parent != null) {
                stateBag = window.parent.$find("MyState1");
                if(stateBag) {
                    stateBag.add_propertyChanged(stateBagProperyChanged);
                }
            }
        }
        function stateBagProperyChanged(sender, args) {
            if(stateBag && args.get_propertyName()=="state") {
                outPutCurrentState("property changed in StateBag:");
            }
        }
        function echo() {
            if(stateBag) {
                // process data from statebag - including parts shared with web service 
                // and main page            
                $get("divOutput").innerHTML = stateBag.get_state().IFrameIO + " - OK.";
            }
        }
        function modify() {
            if(stateBag) {
                var state = stateBag.get_state();
                // process data from statebag - including parts shared with web service 
                // and main page
                state.IFrameIO = "Modified from <b>Iframe</b>, time:" + new Date();
                stateBag.set_state(state);
                stateBag.raisePropertyChanged('state');
            }
        }
        function outPutCurrentState(additionalMessage) {
            var sb = new Sys.StringBuilder();
            sb.appendLine(additionalMessage+"<br>");
            var state = stateBag.get_state();
            for(var key in state) {
                sb.appendLine(String.format("{0}: {1}<br>", key, state[key]));
            }
            $get("divOutput").innerHTML = sb.toString();
        }
    </script>
    <input type="button" id="btnEcho" value="Echo" onclick="echo()" />
    <input type="button" id="btnModify" value="Modify" onclick="modify()" />
    <div id="divOutput"></div>
    </form>
</body>
</html>

Note how we reference the object in the parent window, and even subscribe to it's events. This way we can be in sync with centralized stateBag placed on the main page.

 

And the last important code - how we modify and use state object in the web service:

using System;
using System.Collections;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using Devarchive.Net;

[WebService]
[ScriptService]
public class WebService : System.Web.Services.WebService
{
    [WebMethod]
    public string Echo(Sample2State state)
    {
        // process data from statebag - including parts shared with iframe and main page
        return state.ServiceIO + " - OK.";
    }
    [WebMethod]
    public Sample2State Modify(Sample2State state)
    {
        state.ServiceIO = "modified from <b>web service</b>, time:" + DateTime.Now.ToString();
        // process data from statebag - including parts shared with iframe and main page
        return state;
    }
}

The application will look like this:

image

Yes I know it looks awful :D - I am programmer, not designer :) Hope you understand the idea.

Now let's redraw the diagram I placed in the beginning of the article, and mark the places where the state can be used/modified with red mark:

image

As you see we can access or modify it using javascript from any area in the browser (left side), and also can send/receive and use it in code behinds of iframe pages, main page, and code for web service.

Hope this helps.

Technorati Tags:

No comments: