Saturday, 12 May 2007

Messing with UpdatePanel to speed up and extend Ajax

I was looking for an answer to the problem of a grid inside an update panel. You see, since the rows and cells of a DataGrid or a GridView are special controls that can't be put inside panels, only in specific parent controls like tables and rows, there is no way to update only a row or a cell of a grid. If the grid is big, it takes a long time to render it entirely, it takes the CPU to 100%, it even blocks the animation of gifs. That results in ugly Ajax.

So, my first thought was: is there a way to update only what has changed? As I was saying in a previous post, a Page is rendered as its HTML string the first time it is loaded and then each Ajax postback makes it render like a list of tokens. The token format is this:

length|type|id|content|


For example 100|updatePanel|UpdatePanel1|<inner HTML of panel of 100 bytes>|

What if I would to insert my own tokens, then? Could I, let's say, change the innerHTML of a control outside of the UpdatePanel? And the answer is YES!

There are 20 token types:
  • updatePanel
  • hiddenField
  • arrayDeclaration
  • scriptBlock
  • expando
  • onSubmit
  • asyncPostBackControlIDs
  • postBackControlIDs
  • updatePanelIDs
  • asyncPostBackTimeout
  • childUpdatePanelIDs
  • panelsToRefreshIDs
  • formAction
  • dataItem
  • dataItemJson
  • scriptDispose
  • pageRedirect
  • error
  • pageTitle
  • focus


Most are not interesting, but 4 of them are!

type:updatePanel
If you add a token to the rendered page string that has the type updatePanel and the id is the UniqueID or ClientID of a control, the content will replace the innerHTML of that control, even if the control is not in an UpdatePanel.

type:hiddenField
If you add a token to the rendered page string that has the type hiddenField and the id is the UniqueID or ClientID of a control, the content will replace the value html property of that control. You can use it on hidden fields, but also on any type of input or html element that has a value. If the control does not exist, a hidden input will be created with that id and then the value will be set. You could read that value after a normal PostBack, let's say.

type:expando
If you add a token to the rendered page string that has the type expando a script will be executed in Javascript that looks like this:
id=content

Example: 5|expando|document.getElementById('TextBox1').style.backgroundColor|'red'|
This will result in the change of the background color of the control with id TextBox1 to red.

type:focus
If you add a token to the rendered page string that has the type focus and the content is a ClientID, then the focus will be set to that control provided that the focus.js script has been loaded. This script is loaded when you use Page.SetFocus. So, in order to set the focus to a control using this method, you must use SetFocus in PageLoad on any control you would like.

Why not use SetFocus, then, and be done with it? Well, because this, as all the methods above work on ANY control in the page, not just the ones in the update panel.

And now the code
using System;
using System.IO;
using System.Web.UI;
 
public partial class _Default : Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Use SetFocus so that focus.js is loaded
        SetFocus(TextBox1);
    }
 
    // get the token for a javascript property change
    private string TokenizeProperty(string value, string property)
    {
        return string.Format("{0}|expando|{1}|{2}|", value.Length, property, value);
    }
 
    // get the token for a javascript value change
    private string TokenizeValue(string content, string controlID)
    {
        return string.Format("{0}|hiddenField|{1}|{2}|", content.Length, controlID, content);
    }
 
    // get the token for setting focus to a control through javascript
    private string TokenizeFocus(string controlID)
    {
        return string.Format("{0}|focus||{1}|", controlID.Length, controlID);
    }
 
    // get the token to replace the innerHTML through javascript
    private string TokenizeInnerHtml(string content, string controlID)
    {
        return string.Format("{0}|updatePanel|{1}|{2}|", content.Length, controlID, content);
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        // we only do this in the case of an Async Postback
 
        ScriptManager sm = ScriptManager.GetCurrent(this);
        if ((sm == null) || !sm.IsInAsyncPostBack)
        {
            base.Render(writer);
            return;
        }
 
        // Get the rendered page string 
        // (which should be a list of Ajax tokens)
 
        HtmlTextWriter tw = new HtmlTextWriter(new StringWriter());
        base.Render(tw);
        string content = tw.InnerWriter.ToString();
 
        // Get some meaningless text that changes over time
        string insert = DateTime.Now.ToLongTimeString();
 
        //Change the inner html of Panel2 and some 
        // table cell with the id 'testTD' with the string
        content += TokenizeInnerHtml(insert, Panel2.UniqueID);
        content += TokenizeInnerHtml(insert, "testTD");
 
        // Set value of TextBox2 to the string
        content += TokenizeValue(insert, TextBox2.UniqueID);
 
        // change the background color of TextBox2 to red
        string property = string.Format(
            "document.getElementById('{0}').style.backgroundColor",
            TextBox2.ClientID);
        string value = "'red'";
        content += TokenizeProperty(value, property);
 
        // Set focus to TextBox2
        content += TokenizeFocus(TextBox2.ClientID);
 
        // write the content with the extra tokens
        writer.Write(content);
    }
     
}



Of course, that doesn't solve my initial problem, of speeding up the Ajax rendering of large grids. That's because, even if I would solve the ViewState issues and the quirks that are bound to appear, I still can't change the innerHTML property of tables or table rows, as it is a readonly property.

So where am I to use this? It's easy: first of all, put a button (and only a button) inside an UpdatePanel. Any click on that button will trigger an Ajax postback, but will send a minimal amount of data. Then, put outside the UpdatePanel a Panel. Now you can override the Render of the page and on every Ajax postback, add whatever HTML you want to that panel. If you want to do it from javascript, put the button in a div with style="display:none" and then trigger the button click whenever you want to cause the postback. I am certain that for large readonly grids, that is a way faster method than the putting the grid inside the updatepanel.

To do this the traditional Atlas way you would have had to declare a web service, and then to set a javascript onclick event on the button, that would have executed a WebService method that returned a string, and manually change the innerHTML of the panel.

It happened again... Stargate is dying

My preferred method of watching SciFi is to download it from the Internet. I have these shows that I watch religiously, even if they are not all very good. But given the low quality and scarceness of scifi these days, that's all I've got. One of these shows is Stargate, a series that spun from a Kurt Russel movie and that managed to reach 13 seasons together with its offshoot, Atlantis.

Anyway, my method is to watch for the show air dates, then look for it on the Internet the next day. However, once I happened to find the entire Atlantis series for a few weeks way before the release date. Now it happened again. Stargate and Atlantis episodes till July are on the net. I am now watching them all. The quality is way down, the self-irony of the writers has gone way up, but they are there. What's going on?

Update:
Oh, damn! Having just seen Stargate episode 20 of season 10, I've learned that there is not going to be a season 11 anymore. That being the reason why the show has been released online, probably. SciFi Channel cancelled the show. MGM, the owners of the Stargate francise, promised two Stargate movies, as you can see in this article.

Devastating as this is, considering the total lack of climax or seriousness of both season 3 of Atlantis and season 10 of SG1, ending Stargate to turn it into something better might not be a bad idea. I just lack the confidence that profit driven corporations have the right stuff to create something that should be art and brain driven.

Ah, that's that. Another decent scifi show bites the dust. I'll be watching Battlestar Galactica till it gets completely Lost (pun intended) in new "spiritual" developments.

For the melancholics, watch these videos released on YouTube as 200th Stargate special and this little video, which is a fun one, and I believe fits perfectly with the topic of this article:[another youtube deleted video, guh!]

Thursday, 10 May 2007

The place where we are alone with our thoughts.

I had this English teacher in high school. She was very nice, and I also enjoyed her classes, mainly because I already knew English and because she did the unthinkable, she said she would pass anyone who didn't want to learn English, provided they didn't come to disturb the class. She was a good teacher.

Anyway, one day she started talking about the place where she is alone with her thoughts and she can feel calm. I, being even less tactful than I am today, replied "Oh, the toilet!". She felt offended, because she was talking about church. But the scene stuck with me. And sometimes, on the toilet, alone with my thoughts (and my cat - so not quite alone) I recall her reaction and my own. You see, I understood why she felt offended, it was because I compared a place she considered clean with a place she considered dirty, but I also thought about why a woman that believes in a deity that made us all considers dirty something like we do by design and clean something we do inside a building with trainers and specific rituals that are not by design.

And that got me thinking (hmm, is my blog the best thing since toilets?) about the general situation where we put so much value on an external thing, while the value itself is internal. And I am not talking here only about religion (which by now you know I consider stupid) but also about everything else. Fashion, for example. It's the human equivalent of monkey see monkey do. It's putting value on a thing, person or trend simply because you feel like it. So it's something that has internal value, you gave it importance, but you attached it to something external.

Take another example: the things we get attached to, apparently by brain design. A child is being told that his favourite toy has been cloned into a perfect copy. And he is given "the clone", which is actually the same object. And the kid wants his toy back, not the copy. And the examples are infinite.

I think it's because in our brains, things are not things, but intersections of meanings. A red pill is the intersection between "pill" and "red", themselves intersections of other concepts. The church is nothing but a silly building, but it has meaning, for it gives peace and solitude for a while. So does a bathroom. So does this blog. But there comes a time when we realise that the intersections don't add up. It's called thinking, and it's the equivalent of Bonzai trimming of one's thoughts. We start cutting away the lines that don't make sense or that hurt us, while we strenghten the ones that give us sense and pleasure. We start with a thin scaffolding of chaotic wires and we bring it to a sturdy iron bar cage where our thoughts are stable and protected. That is the real place where we are alone with our thoughts, and we are alone because we don't allow anybody or anything in.

So trim carefully, some thin wires are good while some iron bars are bad.

C# indexed properties - inheriting from VB

VB has a feature that it missing in C#, that is indexed properties. There are various methods to emulate this behaviour in C# and here is a small article about it:
Three C# Tips: Indexed properties, property delegates, and read-only subproperties.

However, a problem arises. What happens if you have a library written in VB and having indexed properties and you try to use it in a C# project? Well, it works, and the generated code is something like:
 public override bool get_PropertyName(string index)
{
}
public override bool set_PropertyName(string index)
{
}


Somehow, this compiles :) Anyway, the problem now is that you can't use the VB code that used to use the VB library if you convert it to C# like this. I haven't found any way to do this:
VBLib(with indexed properties)+VBApplication(inheriting or overriding indexed properties) -> C#Lib(translated)+VBApplication(unchanged).

Wednesday, 9 May 2007

How idiocy leads to death.

I was too tired to work this morning, so I got on Digg to read what people were reading.

And I've seen some interesting articles, like for instance one about Starbucks. They started this campaign in which people write stuff and the company writes it on coffee cups. One man dared to question praying in God, based on the assumption that people are rational beings with a strong will. Obviously that assumption was wrong, as some chick got 'offended' and started bitching about the text. It got me thinking, you know, of why nobody cares if I am offended by all the God crap I hear everywhere. Some guy said on TV a few days ago that in Romania 99.8% of the population are believers. Yeah, right! Like, they are not full time declarative atheists. How easy it is to spin things.

Anyway, back to interesting topics. There was one about how lawyers are behind the times. They write these 'cease and desist' letters, with a threatening language that no one can ever sympathize with, but nowadays these documents get on the Internet, for everyone to read. And hate. So they cause public relation troubles for the companies they were trying to protect. I don't really see a problem, though, as there are a lot more lawyers ready to sue these lawyers so there you go.

But even religious and legal idiocy fades compared to the total mind numbing dumbness of these two Vegan parents. Apparently, they've decided to make their 6 week old infant a Vegan from birth. They fed it soy milk and apple juice. The baby died.

How gullible are we?! How can we believe in all these stupid things and advertise them as indie revolt against 'the system' or 'healthy' lifestyle against the food corporations and so on? Is it so hard to mind your own business and let other people think and decide for themselves?

Tuesday, 8 May 2007

There is a fly on my blog!

Apparently, the summer has brought all kind of nasty insects. A fly has gotten loose on my blog! It's a bfly! Until it finds something useful to do (like allowing people to search stuff or going to the interesting bits or acting as a friendly button) and until it will find friends to join it, it will just annoy us. Alas! It is an unswatabble fly.

Cross browser inner width and height of html documents

I wanted to create this Javascript fly that would... well... fly on the screen. So the first thing I did is create an empty html, put a script tag in it, add some init function to the body and then write the code.
First problem: how to get maximum height and width of the page in both IE and Mozilla. I found a way, then I added a more complex html code, like a DOCTYPE. Well, amazingly (duh!) it didn't work. Finally, after trying several options, I found this code to be working in both browsers and in both doctypes (or lack of). Please report any issues with it, so I can fix it. Thank you.

function maxHeight() {
var h=0;
if (window.document.innerHeight>h)
h=window.document.innerHeight;
if (window.document.documentElement.clientHeight>h)
h=window.document.documentElement.clientHeight;
if (window.document.body.clientHeight>h)
h=window.document.body.clientHeight;
return h;
}
function maxWidth() {
var w=0;
if (window.document.innerWidth>w)
w=window.document.innerWidth;
if (window.document.documentElement.clientWidth>w)
w=window.document.documentElement.clientWidth;
if (window.document.body.clientWidth>w)
w=window.document.body.clientWidth;
return w;
}