First, let's place a <DIV> element on the page. This element needs to have a relative position rather than an absolute position so that it can flow correctly beneath my top navigation. I'll give it a default 1x1 size for the moment, and an ID that I can use easily:
<div style="position: relative; width: 1px; height: 1px;" id="BottomSpacer"> </div>
Now, when the browser window is resized, I want to stretch this DIV out to the new width. First, let's write a function to determine how tall the window is.
function browserWindowHeight()
{
return (window.innerHeight != null) ? window.innerHeight :
(document.documentElement) && document.documentElement.clientHeight ? document.documentElement.clientHeight :
(document.body != null) ? document.body.clientHeight : null;
}
When we know the height of the document, we can stretch the <div> element to the correct height. We want to first determine its position on the page, using a simple function like so:
function findYPos(obj)
{
var cur = 0;
while (obj.offsetParent) {
cur += obj.offsetTop;
obj = obj.offsetParent;
}
return cur;
}
This is a simplified version of the findBothPos() function I used in a previous article. With these two functions, we can now size our div element to the correct height. However, we want to make sure that we only restretch it if the window height has changed, so let's keep track of the window height ourselves and only do this work if we see a difference. More importantly, when we call this function, we'll tell the window to re-call the function every time something changes by attaching ourselves to the window.onresize event:
var _old_height = 0;
function StretchDiv()
{
window.onresize = StretchDiv;
// Only adjust height if something changed
var h = pageHeight();
if (_old_height != h) {
_old_height = h;
// Resize the content area div
var element = $get('BottomSpacer');
var new_height = h - findYPos(d);
if (new_height < 1) new_height = 1;
element.style.height = new_height + 'px';
}
}
Next thing we need to do is to attach this behavior to the page. The best way to do it is with the window.onresize event. Here's how to attach this using Microsoft AJAX:
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(StretchDiv);
Using the add_pageLoaded event is critical - this tells Microsoft AJAX to call the StretchDiv function every time a successful page request is completed. Since many page requests can adjust the size of your window, this may be necessary for you.
Also, remember to play around with the new_height variable. If your div sits between the header and the page footer, you will also need to subtract the footer element's offsetHeight.
| permalink | related link |




( 4.1 / 541 )I mentioned this trick in the previous entry, but it's so useful I thought I'd repeat it.
The easiest way to force an UpdatePanel to refresh via Javascript is with the one line statement of Javascript as follows:
__doPostBack('<%=MyUpdatePanel.ClientID%>','customPostback');
This works for all updatepanels and works on both IE and Firefox. The reason it works is that Microsoft's AJAX toolkit intercepts the call to postback and converts it into an AJAX call. Basically, you're pretending that you're calling MyUpdatePanel.customPostback(); on the server side. However, since that function doesn't exist, the panel just refreshes and the function call is ignored.
| permalink | related link |




( 4.2 / 494 )I'm writing an AJAX application, and I want to have a useful "loading" indicator. However, by default, the ASP.NET Ajax progress indicators display elsewhere on the page, they aren't always easy to see, and they can be a bit confusing at first. Let's walk through how to set up a progress indicator that completely overlaps the element that is being updated with a nifty little transparency and a graphic.
Here's what I'd like to see: when the user clicks on a datagrid, for example, we fade out the datagrid and show a 'Loading' graphic nicely centered on top of the datagrid. So I'll start out by writing the overlay that I'd like to create. Here's a table with all the elements I want:
<table width="100%" height="100%" bgcolor="#FFFFFF">
<tr>
<td align="center" valign="middle">
<img src="/images/animated-progress-icon.gif" width="24" height="24" align=absmiddle />
Updating, please wait...
</td>
</tr>
</table>
Not very complicated, is it? Just a graphic and a bit of text. I put in the background color to white because I want the control to fade to white - if you prefer fading to something else please feel free to play with it. You can also adjust the table's width/height and padding if you want the overlay to be inset. For a graphic, I recommend this online animated progress icon generator to create the image file; it's fast and gives you a few options to match your preferred appearance. Next, let's create necessary ASP.NET AJAX elements:
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true"/>
<asp:UpdatePanel id="MyUpdatePanel" runat="server" UpdateMode="Conditional">
<contentTemplate>
...my controls go here...
<asp:UpdateProgress ID="MyProgressPanel" runat="server" AssociatedUpdatePanelID="MyUpdatePanel">
<ProgressTemplate>
...my 'updating' message table goes here...
</ProgressTemplate>
</asp:UpdateProgress>
</contentTemplate>
</asp:UpdatePanel>
</form>
Now, I'm not sure what element you want to put in your UpdatePanel. In all likelihood, you already know what client script you want to attach, but let's just use something simple for this example. I'll put a link somewhere towards the bottom of the page, outside of the UpdatePanel, and assign it a javascript behavior.
<a href="javascript:OverlayProgressPanel();">test!</a>
When we click this button, we want to force the UpdatePanel to refresh itself. I used to click an invisible button, but this caused problems on Firefox 2.0. Instead, we'll use Microsoft's PostBack method, and we'll cause a delay whenever the page loads so that we can see the progress panel:
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
}
</script>
We need some javascript to attach to the RefreshPanelButton that will place the overlay and trigger the AJAX update. First, we use a simple function called FindBothPos which retrieves the X and Y position of the UpdatePanel. Here's what this looks like:
<script type="text/javascript">
// Find both positions at once
function findBothPos(obj)
{
var curleft = curtop = 0;
if (obj.offsetParent) {
curleft = obj.offsetLeft;
curtop = obj.offsetTop;
while (obj = obj.offsetParent) {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
}
}
return [curleft,curtop];
}
</script>
This findBothPos function is fairly standard and you'll see slight variations of it all over the Internet. It basically finds the relative position of each element and walks the element tree, accumulating positions, until it gets to the top document element.
Since every element in ASP.NET has a dynamically generated client ID, we need to use a combination of server-side and client-side code to get an object reference to a particular item. The ASP.NET has a built-in javascript function called $get() that works across all browsers. But to use $get, we need the ClientID of the element. We can only get the ClientID of the element from ASP.NET server code, so we use <%=ElementName.ClientID%> to retrieve it. Because this produces a literal string in HTML, we surround it with single quotes for use in Javascript. We take the result of this function call and assign it to a local variable named obj. The end result looks like this:
var obj = $get('<%=ElementName.ClientID%>');
Now that we have all the objects, we update the CSS styles of the UpdateProgress so that it completely overlaps the UpdatePanel. We can do that quickly by assigning the obj.style.cssText property - that lets us set any number of css elements all at once. However, doing so overwrites all of your previous styles, so you need to populate the style completely when you use it.
Mind you, we also want to set opacity on this element so that we can see a hint of the old element underneath it. There are three distinct types of opacity - but fortunately they aren't mutually incompatible, so we can just use them all. There is a CSS standard 'opacity' element, and a mozilla-specific '-moz-opacity' element, and an IE-specific 'filter: alpha(opacity=)' element. Just list them all!
Here's the next block of Javascript we want:
<script type="text/javascript">
function OverlayProgressPanel()
{
var update = $get('<%=MyUpdatePanel.ClientID%>');
var progress = $get('<%=MyProgressPanel.ClientID%>');
var pos = findBothPos(update);
progress.style.cssText = 'position: absolute; z-index: 99; opacity: 0.8; -moz-opacity: 0.8; filter: alpha(opacity=80); display: none; left: '
+ pos[0] + 'px; top: ' + pos[1] + 'px; width: ' + update.offsetWidth + 'px; height: ' + update.offsetHeight + 'px';
__doPostBack('<%=MyUpdatePanel.ClientID%>', "customPostback");
}
</script>
Now, when you click the button, the UpdateProgress is dynamically resized to completely overlay whatever you had inside the UpdatePanel, and you get a nifty fade-in/fade-out effect.
*(edit): I found and fixed an annoying bug. The "invisible button" trick I previously used doesn't always work on Firefox, so instead I use the __doPostBack() function and trick the page.
| permalink | related link |




( 3 / 220 )I discovered an unusual problem while developing a website that imports a document from Microsoft Word. My development machine is a standard Intel 32-bit CPU (Core 2 Duo), but of course I thought nothing of it. I wrote a small bit of code that used the Microsoft Word .NET Interop assembly as follows.
In web.config, I added the assembly reference:
<configuration>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Office.Interop.Word, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"/>
In my class file, I launch Microsoft Word:
using Microsoft.Office.Interop.Word;:
...
Microsoft.Office.Interop.Word.Application WordApp = new Application();
This fails on 64-bit windows with the error:
Retrieving the COM class factory for component with CLSID {000209FF-0000-0000-C000-000000000046} failed due to the following error: 80070005.
Although this error makes it sound like a permissions problem, it turns out that it's a 64-bit/32-bit issue. 64-bit IIS doesn't want to run a 32-bit COM component by default. So I did a bit of research and found Microsoft's help page for this problem. Turns out the solution is to run a small script on the 64-bit server:
cd \inetpub\adminscripts
cscript.exe adsutil.vbs set W3SVC/AppPools/Enable32BitAppOnWin64 "true"
However, adsutil.vbs was empty on my server! I don't know how that happened. And we all know that downloading a strange VBS file and executing it is bad, so I found another copy of the file on my Vista machine by doing a file search. So when I change this 32-bit/64-bit flag, all of a sudden everything on my server bombs with a HTTP 500 error. What gives?
Seems like when I made this change, it somehow disabled ASP.NET completely. Apparently, you need to re-register 32-bit IIS to do this. Here's how:
c:\windows\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -i
Next, you have to open IIS Manager. Go to the "Web services Extensions" section, and you'll see ASP.NET v2.0.50727 (32-bit). Select it and click the "Allow" button. And all of a sudden things work ... except that I still can't launch Microsoft Word. Same error.
I'm left with the conclusion that this has to be a 32/64bit problem, but I can't come up with a working solution.
| permalink | related link |




( 2.9 / 236 )Which is more important, truth or utility?
In 1620, in the New Organon, chapter CXXIV, Sir Francis Bacon wrote:
Again, it will be thought, no doubt, that the goal and mark of knowledge which I myself set up (the very point which I object to in others) is not the true or the best, for that the contemplation of truth is a thing worthier and loftier than all utility and magnitude of works; and that this long and anxious dwelling with experience and matter and the fluctuations of individual things, drags down the mind to earth, or rather sinks it to a very Tartarus of turmoil and confusion, removing and withdrawing it from the serene tranquility of abstract wisdom, a condition far more heavenly. Now to this I readily assent, and indeed this which they point at as so much to be preferred is the very thing of all others which I am about. For I am building in the human understanding a true model of the world, such as it is in fact, not such as a man's own reason would have it to be; a thing which cannot be done without a very diligent dissection and anatomy of the world. But I say that those foolish and apish images of worlds which the fancies of men have created in philosophical systems must be utterly scattered to the winds. Be it known then how vast a difference there is (as I said above) between the idols of the human mind and the ideas of the divine. The former are nothing more than arbitrary abstractions; the latter are the Creator's own stamp upon creation, impressed and defined in matter by true and exquisite lines. Truth, therefore, and utility are here the very same things; 2 and works themselves are of greater value as pledges of truth than as contributing to the comforts of life.
I find that the New Organon is the clearest explanation of the principles of science available to mankind. Sir Francis Bacon developed, in this document, the principle that what is true is only that which proves useful: it need not be deducted a priori, nor does it need to withstand revisions in the future. Truth must only be useful and reliable.
| permalink | related link |




( 2.9 / 281 )Back Next

Calendar



