|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Introduction
UFrame does not use IFRAME nor UpdatePanel and thus it is very fast. <div class="UFrame" id="UFrame1" src="SomePage.aspx?ID=UFrame1" > <p>This should get replaced with content from Somepage.aspx</p> </div> Response from See it in action!You can test
What is UFrame?
The above <div id="UFrame1" src="SomePage.aspx" > <p>This should get replaced with content from Somepage.aspx</p> </div> The features of
Download the codeYou can download latest version of Please go to the "Source Code" tab for the latest version. You are invited to join the project and improve it or fix bugs. How to use UFrameYou just need to include three javascripts on your page:
All these comes with the source code of Then you put some "src" attributes on the <div> tags. For example: <div src="AnotherPage.aspx"> <p>Loading Flickr photos...</p> </div> That's it. When the document loads completely, Using UFrame with ASP.NET MVCJust like regular ASP.NET, you can serve MVC handled URLs inside <div class="UFrame" id="UFrame1" src="/SomePage/ABC/View/Omar/Zabir/25" > <p>This should get replaced with content from /SomePage/ABC/View</p> </div> Here the
src attribute points to a MVC handled url. The result is as usual:The MVC View is implemented as following: <body> <div> This is /SomePage output. <p>This is a widget kind of page which can be hosted many times using a unique ID</p> <% using(Html.Form("SomePage", "Update")) { %> <asp:Label runat="server" ID="PostbackLabel" Visible="False" EnableViewState="false" Text="Postback worked!" Font-Bold="true" ForeColor="Red"></asp:Label> <p>Testing inline javascript:<span id="message_<%= ViewData.ID %>" ></span></p> First: <%= Html.TextBox(ViewData.ID + "first", ViewData.First, 30) %><br /> Last: <%= Html.TextBox(ViewData.ID + "last", ViewData.Last, 30)%><br /> Age: <%= Html.TextBox(ViewData.ID + "age", ViewData.Age.ToString(), 3)%><br /> <%= Html.SubmitButton() %> <% } %> </div> </body> As you see, the MVC page works fine and it can read data from Request and post data to the same MVC url. You can use regular MVC libraries including the new helper libraries released with Preview 2.
Error handlingJust like IFRAME,
UFrame can show error pages properly. Following shows when an unhandled exception occurs, UFrame is perfectly capable of parsing the error response and show it inside the container DIV:Internals of UFrameUFrame makes XMLHTTP calls to the URL specified in src attribute. It expects html output from the source. It then parses the html and finds out all inline and external script and stylesheets. Then it injects the stylesheets and scripts into the browser DOM. It then waits until all external scripts are downloaded. When done, it injects the loaded body html inside the DIV and executes all inline scripts. This way, all inline scripts can access the DOM elements properly. When the html is completely loaded and all scripts are executed, it hooks all <form> and <a> tags to make sure the forms do not submit themselves and the hyperlinks do not navigate the browser away. Instead, they are handled to make sure the postback and navigation happens via the UFrame. First step is to find out all the DIVs that want to be
UFrames. For each DIV, one instance of UFrame class is created.$('div[@src]',document).each(function() { var container = $(this); var id = container.attr("id"); if( null == UFrameManager._panels[id] ) { UFrameManager.init({ id: id, loadFrom: container.attr("src"), initialLoad : "GET", progressTemplate : container.attr("progressTemplate") || null, showProgress : container.attr("showProgress") || false, beforeLoad: function(url,data) { return eval(container.attr("beforeLoad") || "true") }, afterLoad: function(data, response) { return eval(container.attr("afterLoad") || "true") }, beforePost: function(url,data) { return eval(container.attr("beforePost") || "true") }, afterPost: function(data, response) { return eval(container.attr("afterPost") || "true") }, params : null, beforeBodyTemplate : container.attr("beforeBodyTemplate") || null, afterBodyTemplate : container.attr("afterBodyTemplate") || null }); } }); Here you see The UFrameManager =
{
_panels : {},
empty : function() {},
init : function(config)
{
var o = new UFrame(config);
UFrameManager._panels[config.id] = o;
o.load();
},It keeps a mapping of each Next UFrame.prototype = {
load : function()
{
var c = this.config;
if( c.loadFrom )
{
UFrameManager.loadHtml(c.loadFrom, c.params, c);
}
},
loadHtml : function(url, params, config)
{
var container = $('#' + config.id);
var queryString = $.param(params || {});
if((config.beforeLoad || UFrameManager.empty)(url, params) !== false)
{
//if(config.progressTemplate) container.html(config.progressTemplate);
UFrameManager.getHtml(url, queryString, function(content)
{
(config.afterLoad || UFrameManager.empty)(url, content);
UFrameManager.processHtml(content, container, config);
});
}
},Here it makes an XMLHTTP call using the getHtml : function(url, queryString, callback)
{
try
{
$.ajax({
url: url,
type: "GET",
data: queryString,
dataType: "html",
success: callback,
error: function(e) { alert("error! " + e); },
cache: true
});
} catch(e) {
alert(e);
}
},The real challenge is properly parsing the HTML and then loading and executing the javascripts and stylesheets received from the response. There are lots of hacks and tricks involved in making this work successfully across all browsers. Fortunately, most of it is already handled by jQuery especially the complex steps in loading external script and waiting for it until it downloads properly and then executing the script in a cross browser way. The processHtml : function(content, container, config)
{
var result = UFrameManager.parseHtml(content, config);
var head = document.getElementsByTagName('head')[0];
var result = { body : "", externalScripts : [], inlineScripts : [],
links : [], styles : [] };Next step is to inject all inline $(result.styles).each(function(index,text) { var styleNode = document.createElement("style"); styleNode.setAttribute("type", "text/css"); if(styleNode.styleSheet) // IE { styleNode.styleSheet.cssText = text; } else // w3c { var cssText = document.createTextNode(text); styleNode.appendChild(cssText); } head.appendChild(styleNode); }); Here you see the cross browser way of injecting CSS to the browser DOM. IE has a special way of taking CSS text. First you need to create a style tag and then use IE's proprietary "stylesheet" property to set the CSS text. All other browsers just take CSS as a text node. Next black art is adding $(result.links).each(function(index,attrs) { window.setTimeout(function() { var link = document.createElement('link'); for( var i = 0; i < attrs.length; i ++ ) { var attr = attrs[i]; link.setAttribute("" + attr.name, "" + attr.value); } if( link.href ) if( !UFrameManager.isTagLoaded('link', 'href', link.href) ) head.appendChild(link); }, 0); }); Here
window.setTimeout is used to execute the code within window object's context. If you don't do this, IE6 just hangs when you attempt to set the "href" attribute of a link tag.Next step is to load all external Javascripts and wait until they are all loaded.
var scriptsToLoad = result.externalScripts.length; $(result.externalScripts).each(function(index, scriptSrc) { if( UFrameManager.isTagLoaded('script', 'src', scriptSrc) ) { scriptsToLoad --; } else { $.ajax({ url: scriptSrc, type: "GET", data: null, dataType: "script", success: function(){ scriptsToLoad--; }, error: function(){ scriptsToLoad--; }, cache: true }); } }); The counter
scriptsToLoad is 0 when all external scripts get loaded either successfully or some fails to load. So, when scriptToLoad is 0, we can proceed with adding the body html inside the container DIV and executing the inline scripts. The cross browser dark magic of loading external scripts property is handled by jQuery. Just for you information, adding a <script> tag to the <head> tag is not all you need to do. For some browser like Safari, you have to make XMLHTTP call to load external Javascript and then call eval to execute the Javascript on window object's scope. The final piece of dark magic is in injecting the body html, executing inline scripts and then hooking all forms and hyperlinks. Here's how they are done:
// wait until all the external scripts are downloaded UFrameManager.until({ test: function() { return scriptsToLoad === 0; }, delay: 100, callback: function() { // render the body var html = (config.beforeBodyTemplate||"") + result.body + (config.afterBodyTemplate||""); container.html(html); window.setTimeout( function() { // execute all inline scripts $(result.inlineScripts).each(function(index, script) { $.globalEval(script); }); UFrameManager.hook(container, config); if( typeof callback == "function" ) callback(); }, 0 ); } }); Here you see, the inline script execution and hooking forms and hyperlink are deferred using a timer because not all browsers immediately make the DOM available to Javascript when a large amount of HTML is injected into the DOM using
Here's the grand hook : function(container, config)
{
// Add an onclick event on all <a>
$("a", container)
.unbind("click")
.click(function()
{
var href = $(this).attr("href");
if( href )
{
if (href.indexOf('javascript:') !== 0)
{
UFrameManager.loadHtml(href, null, config);
return false;
}
else if(UFrameManager.executeASPNETPostback(this, href))
{
return false;
}
else
return true;
}
else
{
return true;
}
});
// Hook all button type things that can post the form
$(":image,:submit,:button", container)
.unbind("click")
.click(function()
{
return UFrameManager.submitInput(this);
});
// Only for IE6 : enter key invokes submit event
$("form", container)
.attr("iPanelId", config.id)
.unbind("submit")
.submit(function() {
var firstInput = $(":image,:submit,:button", container).get(0);
return UFrameManager.submitInput(firstInput);
} );
}Here are the steps:
You might be curious to show how I intercept the executeASPNETPostback : function(input, href)
{
if(href.indexOf("__doPostBack") > 0 )
{
// ASP.NET Postback. Collect the values being posted and submit them manually
var parts = href.split("'");
var eventTarget = parts[1];
var eventArgument = parts[3];
var form = $(input).parents("form").get(0);
form.__EVENTTARGET.value = eventTarget;
form.__EVENTARGUMENT.value = eventArgument;
UFrameManager.submitForm( form, null );
return true;
}
else
{
return false;
}
},Basically the idea is to find the var theForm = document.forms['somePageForm']; if (!theForm) { theForm = document.somePageForm; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } As you see, what ASP.NET does, UFrame does the same thing, only in AJAX way. Now that you have heard many times about submitting the form in AJAX way, here's how it is done: submitForm : function( form, submitData )
{
// Find all checked checkbox, radio button, text box, hidden fild,
// password box and submit button
// collect all their names and values
var params = {};
$(form)
.find("input[@checked], input[@type='text'], input[@type='hidden'],
input[@type='password'], option[@selected], textarea")
.filter(":enabled")
.each(function() {
params[ this.name || this.id ||
this.parentNode.name || this.parentNode.id ] = this.value;
});
if( submitData )
params[ submitData.name ] = submitData.value;
var iPanelId = $(form).attr("iPanelId");
var panel = UFrameManager.getPanel(iPanelId);
var config = panel.config;
var container = $('#' + config.id);
var url = form.action;
if((config.beforeLoad || UFrameManager.empty)(url, params) !== false)
{
if(config.progressTemplate) container.html(config.progressTemplate);
$.post(url, params, function(data)
{
(config.afterLoad || UFrameManager.empty)(url, data);
UFrameManager.processHtml(data, container, config);
});
}
}Here are the steps:
Serving pages as widgets using UFrameUFrame renders a page inside a DIV. As a result, it's a great way to widgetize your pages. You can build small independent pages that can be loaded via UFrame to behave like widgets. In the example, I have shown such a widget which shows photos from Flickr. However, when you load same page more than once within the same document using UFrame, you will run into HTML element ID conflicts. ASP.NET page serves HTML elements with fixed ID. So, loading two instances of the same page will result in duplicate element IDs on the main page. For example, if a page emits a button with ID "ClickMe", if you load two instances of that page using two UFrames, there will be two buttons with ID "ClickMe". This will prevent proper postback and event firing. In order to solve this, each instance of the page needs to produce controls with some unique ID. SomePage.aspx solves this problem by taking an ID in the query string and using that ID to prefix all ASP.NET Control's ID. For example, the first instance of SomePage.aspx is added using UFrame1 ID: <div class="UFrame" id="UFrame1" src="SomePage.aspx?ID=UFrame1" > <p>This should get replaced with content from Somepage.aspx</p> </div> The second instance is added using a different ID: <div class="UFrame" id="UFrame2" src="SomePage.aspx?ID=UFrame2"> <p>This should get replaced with content from Somepage.aspx</p> </div> The public partial class SomPage : System.Web.UI.Page { protected override void AddedControl(Control control, int index) { if (control is HtmlForm) { foreach (Control c in control.Controls) { if (c.ID != null && c.ID.Length > 0) { c.ID = Request["ID"] + c.ID; } } } base.AddedControl(control, index); } Using this approach, you can host many instances of same page on the main page and thus make those small pages behave like widgets. ReferencesThe ConclusionUFrame fills in the shortcoming of AJAX and
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||