HTML Lazy Load

by trocphunc 15. December 2010 23:04

If you’re developing a web app that requires to load and show large HTML chunks (for example, an e-book reader), this trick may help your app to be more responsive, especially on mobile devices.

Let’s say, for example, you have load and show a few chapters for Leo Tolstoy’s “War and Peace” book (500KB HTML page) on iPhone. On my test environment (iPhone 2G with v3.1 firmware, files are loaded directly from iPhone to exclude network lags) the page parsing and rendering took about 7 seconds. During this time, user see a blank page and frozen interface; after the page is loaded the interface interaction is still clunky and requires some time to smooth again. In other words, user perception is really poor.

Why so slow?

What makes page rendering so slow? Well, it’s pretty simple: when HTML page is loaded, browser needs to do a lot work. It has to parse HTML, build elements collections (so things like getElementsByTagName() can work faster), match CSS rules, etc., etc. And then, finally, render all those elements—you may know this process as repaint. Repainting is one of the slowest process in browsers and it’s extremely slow on mobile devices. Thus, you have to avoid repaints in mobile browsers at all costs if you want to create a fast and responsive web-site.

But let’s look at this problem from the other side. Mobile devices has very limited display resolution. You don’t need to show all 500KB text at once, you can pick a first few sentences and push them to the screen so the user can start reading while browser parses the rest of the page.

Lazy load to the rescue

To make all this large text invisible to the browser, all we have to do is to comment it:

<body>
<!--
<p>Well, Prince, so Genoa and Lucca are now just family estates...</p>
-->

</body>

When the text content was commented out, the page parsing took about 350 ms on my test environment. In this time a had a completely parsed and loaded page so I can work with it with JavaScript.

So, we have a commented text, what now? Actually, HTML comment is not just a hidden code chunk, it’s a DOM node which can be easily accessed. Now we need to find this node and parse its content into a DOM tree:

var elems = document.body.childNodes;

for (var i = 0, il = elems.length; i < il; i++) {
var el = elems[i];
if (el.nodeType == 8) { //it’s a comment
var div = document.createElement('div');
div.innerHTML = el.nodeValue;
// now DIV element contains parsed DOM elements so we can work with them
break;
}
}

Since such plain text parsing doesn’t require browser to do CSS matching, repainting and other stuff that it normally does on page parsing, it also performs very fast: about 200 ms in my test environment.

Thus, page loading and parsing took about 550 ms versus 7000 ms in first example and you have a full page’s DOM tree. Now you can pop a few sentences from the list push them to the screen so the user can start reading immediately. The rest elements can be added to the page progressively, chunk by chunk with setTimeout() to prevent interface blocking. My mobile e-book reader engine uses more complex technique: it calculates every element’s dimensions, expands viewport height and adds only those elements that user can see and scroll to, while removing all the rest elements. This technique also increases interface speed and responsiveness and reduces memory footprint

Tags:

Dev

jQuery Mobile & ASP.NET

by trocphunc 10. December 2010 23:41

start using jQuery Mobile.

First, you'll want to create a Visual Studio Solution ... I simply created an ASP.NET Web Application project and added a few directories.  You can see these directories in the screenshot below:

Ok, so I added an "Assets" directory ... inwhich I created a few sub-directories to store the assets of the project.  Now, you don't 'have' to set it up this way, however I've found this setup to be optimal in organizing things.  You'll also see that I created a CSS file, "custom-styles.css" ... to store any additional CSS styles that I may (or may not) add.  I've also referenced this CSS file in the "default.aspx" page.

In your default.aspx page, you're going to want to strip out everything that exists in there and replace it with the following "jQuery Mobile Template" code:

And here is a breakdown of this template code:

To get the most out of of the jQuery Mobile framework, you're going to want to use an HTML5 declaration. Note, that older browsers that don't support HTML5 will ignore the DOCTYPE and any other custom attributes you add.

 

1 <!DOCTYPE html>

In the HEAD section, you will be adding the references to 'jquery,' 'jquery mobile,' and the 'mobile CSS theme.'  I also recommend adding a reference to your own custom CSS stylesheet as shown below:

 

1 <head>
2     <title>Page Title</title>
3     <link href="/assets/scripts/jquery.mobile-1.0a2/jquery.mobile-1.0a2.min.css" rel="stylesheet" type="text/css" />
4     <script src="/assets/scripts/jquery/jquery-1.4.4/jquery-1.4.4.min.js" type="text/javascript"></script>
5     <script src="/assets/scripts/jquery.mobile-1.0a2/jquery.mobile-1.0a2.min.js" type="text/javascript"></script>
6     <link href="/assets/css/custom-styles.css" rel="stylesheet" type="text/css" />
7 </head>


And for the BODY section, you'll be adding the standard jQuery Mobile page template:

 

01 <body>
02  
03 <!-- start page -->
04 <div data-role="page">
05  
06   <!-- start header -->
07  <div data-role="header">
08       <h1>Page Title</h1>
09  </div>
10     <!-- end header -->
11  
12    <!-- start content -->
13     <div data-role="content">
14         <p>Page content.</p>       
15     </div>
16     <!-- end content -->
17  
18   <!-- start footer -->
19  <div data-role="footer">
20       <h4>Page Footer</h4>
21     </div>
22     <!-- end footer -->
23     
24 </div>
25 <!-- end page -->
26  
27 </body>


And when you put it all together, here is what the standard jQuery Mobile page template looks like:

 

01 <!DOCTYPE html>
02 <html>
03  <head>
04     <title>Page Title</title>
05         <link href="/assets/scripts/jquery.mobile-1.0a2/jquery.mobile-1.0a2.min.css" rel="stylesheet" type="text/css" />
06         <script src="/assets/scripts/jquery/jquery-1.4.4/jquery-1.4.4.min.js" type="text/javascript"></script>
07         <script src="/assets/scripts/jquery.mobile-1.0a2/jquery.mobile-1.0a2.min.js" type="text/javascript"></script>
08         <link href="/assets/css/custom-styles.css" rel="stylesheet" type="text/css" />
09     </head>
10 <body>
11  
12 <!-- start page -->
13 <div data-role="page">
14  
15     <!-- start header -->
16  <div data-role="header">
17       <h1>Page Title</h1>
18  </div>
19     <!-- end header -->
20  
21    <!-- start content -->
22     <div data-role="content">
23         <p>Page content.</p>       
24     </div>
25     <!-- end content -->
26  
27   <!-- start footer -->
28  <div data-role="footer">
29       <h4>Page Footer</h4>
30     </div>
31     <!-- end footer -->
32     
33 </div>
34 <!-- end page -->
35  
36 </body>
37 </html>

Now, take the standard jQuery Mobile page template, and add it to your default.aspx page in your ASP.NET Web Application Project.  Save it, build your project, and view the default.aspx page in your browser.  You should see something like this:

From here, you'll be able to start adding controls, HTML, content, etc. to your jquery mobile application ... which I'll cover in my next post.

Tags:

Dev

Read feed C# 3.5, 4.0

by trocphunc 5. November 2010 20:35

XmlReader reader = XmlReader.Create(curUrl);
SyndicationFeed feed = SyndicationFeed.Load(reader);
string title = String.Empty;
string link = String.Emplty;
DateTime dateTime = null;
foreach (var feeditem in feed.Items)
{
title = feeditem.Title.Text;
dateTime = feeditem.PublishDate.LocalDateTime;
link = feeditem.Links[feeditem.Links.Count -1].Uri.OriginalString;

//Do your tasks here
}

Tags:

Dev

Header Access in ASP.NET

by trocphunc 1. November 2010 16:50

I am easy to please when it comes to small and simple things that make my life as a developer easier. For example, I came accross something I had not noticed before in ASP.NET while reading a post from Dave Burke. The HtmlHead class exposed by the Page class as Page.Header. I love this. It makes it so easy to get to, and manipulate the header attributes for a page. A simple act of changing the page's title, style, etc before was a pain. Now it's just setting a few properties.

To change a page's title:

this.Header.Title = "This is the new page title.";

To add a style attribute for the page:

Style style = new Style();
style.ForeColor = System.Drawing.Color.Navy;
style.BackColor = System.Drawing.Color.LightGray;

// Add the style to the header for the body of the page
this.Header.StyleSheet.CreateStyleRule(style, null, "body");

To add a stylesheet to :

HtmlLink link = new HtmlLink();
link.Attributes.Add("type", "text/css");
link.Attributes.Add("rel", "stylesheet");
link.Attributes.Add("href", "~/newstyle.css");
this.Header.Controls.Add(link);

Simple and elegant. I guess I can finally get rid of my helper classes to do all of that and keep things simpler. Sad that I had not noticed that was there in 2.0 earlier.

Tags: ,

Dev

Default Image when No Image Found

by trocphunc 1. November 2010 15:36

 

Here’s a solution to display a Default Image in the Image control, when the original image is not available. The same solution can be applied to both ASP.NET Image Controls as well as HTML image controls

ASP.NET Image Control

<asp:Image ID="imgProd" runat="server"
onerror="this.onload = null; this.src='ImageurlORAnyImageHere.jpg';"/>

HTML Image Control

<img id="imgProd1" src="someimage.gif"
onerror="this.onerror=null; this.src='ImageurlORAnyImageHere.jpg';" />

We are using the onerror event of the image tag, which triggers if there is an error while loading an image. If an error occurs, we display 'ImageurlORAnyImageHere.jpg' as the default image. You can even add an image url.

If you are looking out to apply the same technique while loading images in an ASP.NET GridView, check my article Loading Images Asynchronously Inside an ASP.NET GridView

Tags:

Dev

Using CNAMES to get around browser connection limits

by trocphunc 30. October 2010 00:30

Ryan Breen has written up a detailed post on Circumventing browser connection limits for fun and profit in which he discusses the old-fashion limits of 2 connections per HTTP/1.1 per host, and the benefit you get from a simple CNAME hack.

The average load time when using 2 connections is 7.919 seconds. The average load time when using 6 connections is 4.629 seconds. That’s a greater than 40% drop in page load time. This technique will work anywhere that you have a large block of object requests currently served by one host.

There is plenty of precedent for this approach in real world Ajax apps. To exploit connection parallelism, the image tiles at Google Maps are served from mt0.google.com through mt3.google.com. Virtual Earth also uses this technique.

You can also use this connection management approach to sandbox the performance of different parts of your application. If you have page elements that require database access and may be more latent than static objects, keep them from clogging up the 2 connections for image content by putting them on a subdomain. This trick won’t cause a huge improvement in the total load time of your page, but it can significantly improve the perceived performance by allowing static content to load unfettered.

Tags:

Dev

Gmail-like loading indicator with ASP.NET Ajax

by trocphunc 22. October 2010 16:22

At the moment I'm working on making a web application we just developed more user friendly and more appealing to the end users.

The application uses a few ASP.NET Ajax controls so I was pretty surprised when the customer sent me an email saying that he liked all the dynamic loading and the fact that he could reorder "things" using drag&drop and saving them without waiting the page to reload, but it took him a while to understand was going on. The first time he clicked the button, and since nothing happened, he thought that something was going wrong, so he kept clicking on the button, an yet nothing happening.

The problem was that since all the Ajax interactions happen behind the scenes asynchronously, the user doesn't understand what's going on: sometimes the user doesn't need to know what's going on (like when you are just reloading some data), but when he presses a button he needs to know that he did the right thing and that something is happening. With the "old style" ASP.NET a postback would have been initiated, so it was obvious that something was happening, but how to do it using Ajax?

That is pretty easy to accomplish with ASP.NET: just drop in an UpdateProgress control and it will be displayed when an Ajax postback happens.

But, as default behavior, the UpdateProgress is displayed in the position where it is added to the page, so, if your page is longer than a scroll page, the indicator could not be visible: it has to be positioned relative to the browser window and not relative the html document.
At first I looked at how to hook into the script that displays the div in order to change the position via script, but while chatting with Daniela (which is an UIX and web designer) she said: "Why don't you just use the fixed positioning CSS attribute?"

And she was right, much easier than hooking into ASP.NET Ajax scripts. I definitely need to improve my CSS knowledge smile_regular.

Step 1 - Add an UpdateProgress control to the page

First thing you have to add an UpdateProgress to your ASP.NET page.

<asp:UpdateProgress ID="UpdateProgress1" runat="server"
DynamicLayout="false">
<ProgressTemplate>
<img src="Images/ajax-loader.gif" /> Loading ...
</ProgressTemplate>
</asp:UpdateProgress>

You can put this code anywhere inside the page, but I suggest you put it at the end. I didn't specify any AssociatedUpdatePanelID because I want the loading indicator to appear for all the UpdatePanel I have on the page: if you want it only for some of them you have to set it.

Step 2 - Add CSS

 #UpdateProgress1 {
background-color:#CF4342;
color:#fff;
top:0px;
right:0px;
position:fixed;
}

#UpdateProgress1 img {
vertical-align:middle;
margin:2px;
}

The trick is to set the position attribute to fixed: it will make the top and right dimensions relative to the browser window and not to its original position (as with relative) or to containing element (as with absolute).
This CSS will put the indicator on the top right corner of the window, as the Gmail indicator.

Step 3 - Add an activity indicator image

But we can do it even better then gmail, we could add a nice animated activity indicator. Last year ScottGu recommended a few websites to download some indicators from, and I really liked www.ajaxload.info. This site dynamically generate an activity indicator for you, with the type and colors you prefer.

This is the animated gif I created to fit nicely into my UpdateProgress control:

And this is how the final Gmail-like loading indicator looks like:

 

Loading ...

Download the code sample

UPDATE: I changed the CSS after Lorenzo comment adding a few pixel around the image and vertically aligning the text

UPDATE2:Laurent Duveau wrote a nice post on how to make it work with IE6, using the AlwaysVisibleControl that is inside the AJAX Control Toolkit: http://weblogs.asp.net/lduveau/archive/2007/05/25/ajax-loading-indicator-like-gmail.aspx

Tags: , ,

Dev

Using AntiXss As The Default Encoder For ASP.NET

by trocphunc 22. October 2010 15:53

Scott Guthrie recently wrote about the new <%: %> syntax for HTML encoding output in ASP.NET 4. I also covered the topic of HTML encoding code nuggets in the past as well providing some insight into our design choices for the approach we took.

A commenter to Scott’s blog post asked,

Will it be possible to extend this so that is uses libraries like AntiXSS instead? See: http://antixss.codeplex.com/

The answer is yes!

ASP.NET 4 includes a new extensibility point which allows you to replace the default encoding logic with your own anywhere ASP.NET does encoding.

All it requires is to write a class which derives from System.Web.Util.HttpEncoder and register that class in Web.config via the encoderType attribute of the httpRuntime element.

Walkthrough

In the following section, I’ll walk you through setting this up. First, you’re going to need to download the AntiXSS library which is at version 3.1 at the time of this writing. On my machine, that dropped the AntiXSSLibrary.dll file at the following location: C:\Program Files (x86)\Microsoft Information Security\Microsoft Anti-Cross Site Scripting Library v3.1\Library

Create a new ASP.NET MVC application (note, this works for *any* ASP.NET application). Copy the assembly into the project directory somewhere where you’ll be able to find it. I typically have a “lib” folder or a “Dependencies” folder for this purpose. Right clicke on the References node of the project to add a reference to the assembly.

add-reference Add-Reference-dialogThe next step is to write a class that derives from HttpEncoder. Note that in the following listing, some methods were excluded which are included in the project.

using System;
using System.IO;
using System.Web.Util;
using Microsoft.Security.Application;

/// <summary>
/// Summary description for AntiXss
/// </summary>
public class AntiXssEncoder : HttpEncoder
{
public AntiXssEncoder() { }

protected override void HtmlEncode(string value, TextWriter output)
{
output.Write(AntiXss.HtmlEncode(value));
}

protected override void HtmlAttributeEncode(string value, TextWriter output)
{
output.Write(AntiXss.HtmlAttributeEncode(value));
}

protected override void HtmlDecode(string value, TextWriter output)
{
base.HtmlDecode(value, output);
}

// Some code omitted but included in the sample
}

Finally, register the type in web.config.

...
<system.web>
<httpRuntime encoderType="AntiXssEncoder, AssemblyName"/>
...

Note that you’ll need to replace AssemblyName with the actual name of your assembly. Also, in the sample included with this blog post, AntiXssEncoder is not in any namespace. If you put your encoder in a namespace, you’ll need to make sure to provide the fully qualified type name.

To prove that this is working, run the project in the debugger and set a breakpoint in the encoding method.

debugger-breakpoint

With that, you are all set to take full control over how strings are encoded in your application.

Note that Scott Hanselman and I gave a live demonstration of setting this up at Mix 10 this year as part of our security talk if you’re interested in watching it.

As usual, I’ve provided a sample ASP.NET MVC 2 project for Visual Studio 2010 which you can look at to see this in action.

Tags: , , ,

Dev

Application FOR XML PATH statement

by trocphunc 20. October 2010 22:38

We all know that in SQL Server using FOR XML PATH statement to be able to query XML data in data generation, the following are examples of some of its applications.

DECLARE @ TempTable DECLARE @ TempTable table ( UserID table (UserID int , UserName int, UserName nvarchar ( 50 )) ; nvarchar (50));
insert insert into @ TempTable into @ TempTable ( UserID , UserName ) (UserID, UserName) values values ( 1 , ' a ' ) (1, 'a')
insert insert into @ TempTable into @ TempTable ( UserID , UserName ) (UserID, UserName) values values ( 2 , ' b ' ) (2, 'b')

select select UserID , UserName UserID, UserName from @ TempTable from @ TempTable FOR FOR XML XML PATH PATH

Run this script will generate the following results:

< row > <Row>
< UserID > 1 </ UserID > <UserID> 1 </ UserID>
< UserName > a </ UserName > <UserName> a </ UserName>
</ row > </ Row>
< row > <Row>
< UserID > 2 </ UserID > <UserID> 2 </ UserID>
< UserName > b </ UserName > <UserName> b </ UserName>
</ row > </ Row>

We can see that the data generated two lines of the two  Node, and edit the parameters of PATH:

select select UserID , UserName UserID, UserName from @ TempTable from @ TempTable FOR FOR XML XML PATH ( ' lzy ' ) PATH ('lzy')

Run the script will generate the following results:

< lzy > <Lzy>
< UserID > 1 </ UserID > <UserID> 1 </ UserID>
< UserName > a </ UserName > <UserName> a </ UserName>
</ lzy > </ Lzy>
< lzy > <Lzy>
< UserID > 2 </ UserID > <UserID> 2 </ UserID>
< UserName > b </ UserName > <UserName> b </ UserName>
</ lzy > </ Lzy>

Can see the node becomes , PATH()  In fact, PATH () parameter is in brackets the name of the control node, so we can look at if it is an empty string (not no arguments), what would result?

select select UserID , UserName UserID, UserName from @ TempTable from @ TempTable FOR FOR XML XML PATH ( '' ) PATH ('')

This script will generate the implementation of the above results:

< UserID > 1 </ UserID > <UserID> 1 </ UserID>
< UserName > a </ UserName > <UserName> a </ UserName>
< UserID > 2 </ UserID > <UserID> 2 </ UserID>
< UserName > b </ UserName > <UserName> b </ UserName>

So as not to show a higher level node, and we know PATH mode, the column name or column alias is used as XPath expressions to deal with, that is Is the name of the column, so do not give a bold experiment with the specified column names and aliases will be like?

select select CAST ( UserID CAST (UserID AS AS varchar ) + '' , UserName + '' varchar) +'', UserName +'' from @ TempTable from @ TempTable FOR FOR XML XML PATH ( '' ) PATH ('')

The results of running the above sentence will be generated

1a2b 1a2b

All data generated a line, but not connected characters, such data may be useless to everyone, you can then change this:

select select CAST ( UserID CAST (UserID AS AS varchar ) + ' , ' , UserName + '' , ' ; ' varchar) + ',', UserName +'', ';' from @ TempTable from @ TempTable FOR FOR XML XML PATH ( '' ) PATH ('')

The results generated

1,a;2,b; 1, a; 2, b;

We now understand it, you can control parameters to generate the results you want, for example:

select select ' { ' + CAST ( UserID '{' + CAST (UserID AS AS varchar ) + ' , ' , ' " ' + UserName + ' " ' , ' } ' varchar) + ',', '"' + UserName + '"', '}' from @ TempTable from @ TempTable FOR FOR XML XML PATH ( '' ) PATH ('')

The results generated

{1,"a"}{2,"b"} {1, "a"} {2, "b"}

Can also generate other formats, you can mix the format they need.

Here is an application of statistics, I hope you can think of more instances of the following applications

DECLARE @ T1 DECLARE @ T1 table ( UserID table (UserID int , UserName int, UserName nvarchar ( 50 ) , CityName nvarchar (50), CityName nvarchar ( 50 )) ; nvarchar (50));
insert insert into @ T1 into @ T1 ( UserID , UserName , CityName ) (UserID, UserName, CityName) values (1, 'a', 'Shanghai')
insert insert into @ T1 into @ T1 ( UserID , UserName , CityName ) (UserID, UserName, CityName) values (2, 'b', 'Beijing')
insert insert into @ T1 into @ T1 ( UserID , UserName , CityName ) (UserID, UserName, CityName) values  (3, 'c', 'Shanghai')
insert insert into @ T1 into @ T1 ( UserID , UserName , CityName ) (UserID, UserName, CityName) values  (4, 'd', 'Beijing')
insert insert into @ T1 into @ T1 ( UserID , UserName , CityName ) (UserID, UserName, CityName) values (5, 'e', 'Shanghai')

SELECT SELECT B . CityName , LEFT ( UserList , LEN ( UserList ) - 1 ) B. CityName, LEFT (UserList, LEN (UserList) - 1) FROM FROM ( (
SELECT SELECT CityName , CityName,
( SELECT (SELECT UserName + ' , ' UserName + ',' FROM @ T1 FROM @ T1 WHERE WHERE CityName = A . CityName CityName = A. CityName   FOR FOR XML XML PATH ( '' )) PATH ('')) AS AS UserList UserList
FROM @ T1 FROM @ T1 A A
GROUP GROUP BY BY CityName CityName
) ) B B

Generate results (user name each city)

Tags: ,

Dev

Find and Delete Duplicate Rows From Tables

by trocphunc 18. October 2010 18:17

Someone recently asked me how to find and delete duplicate rows from sample tables, which do not have a Primary Key. I have written one such sample on MSDN code Find and/or Delete Duplicate Rows which I will share here

SAMPLE Data

-- Suppress data loading messages
SET NOCOUNT ON

-- Create Table
CREATE TABLE #Customers (ID integer, CustName varchar(20), Pincode int)

-- Load Sample Data in Table
INSERT INTO #Customers VALUES (1, 'Jack',45454 )
INSERT INTO #Customers VALUES (2, 'Jill', 43453)
INSERT INTO #Customers VALUES (3, 'Tom', 43453)
INSERT INTO #Customers VALUES (4, 'Kathy', 22343)
INSERT INTO #Customers VALUES (5, 'David', 65443)
INSERT INTO #Customers VALUES (6, 'Kathy', 22343)
INSERT INTO #Customers VALUES (7, 'Kim', 65443)
INSERT INTO #Customers VALUES (8, 'Hoggart', 33443)
INSERT INTO #Customers VALUES (9, 'Kate', 61143)
INSERT INTO #Customers VALUES (10, 'Kim', 65443)

To indentify duplicate rows, the trick is to Group the rows by CustName, Pincode. Rows having similar CustName and Pincode will have more than one rows in the grouping. So locate them using HAVING COUNT(*) > 1. If duplicate values are encountered, return the maximum ID for each duplicate row. Using the outer query, delete any ID returned by subquery.

-- Find Duplicate Rows
SELECT MAX(ID) as ID, CustName, Pincode FROM #Customers
GROUP BY CustName, Pincode
HAVING COUNT(*) > 1

-- Delete Duplicate Rows
DELETE FROM #Customers
WHERE ID IN
( SELECT MAX(ID) FROM #Customers
GROUP BY CustName, Pincode
HAVING COUNT(*) > 1)

OUTPUT

image 

Tags: ,

Dev