Welcome to Atalasoft Community Sign in | Join | Help

Using YUI Test to unit test Atalasoft WebControls

Yesterday I read an informative article on how to get started using YUI Test to write unit tests for JavaScript, by Nicholas C. Zakas.  After reading this article, I decided to give it a try, by testing some functions of the WebImageViewer and the WebThumbnailViewer controls.

It took me all of about 15 minutes to write this demo, while following the instructions, and a few more minutes polishing it up for this post.  I am really impressed how easy YUI Test is to work with, and how quickly I was able to get up and running.

Kudos to the YUI team!

Here's a link to my online YUI Test Unit Testing Demo, and here's the code:

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>YUI Test Unit Testing Demo</title>
  <!--CSS-->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.6.0/build/logger/assets/logger.css" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.6.0/build/yuitest/assets/testlogger.css" />

<!-- Combo-handled YUI JS files: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/logger/logger-min.js&2.6.0/build/yuitest/yuitest-min.js"></script>
</head>
<body>
  <form id="form1" runat="server">
  <div>Using YUI Test to Unit Test Atalasoft WebControls</div>
  <div style="height:800px;">
  <table border="0" cellpadding="0" cellspacing="2" style="width:100%; height:100%">
  <tr><td colspan="2" style="width:100%; height:36px;">
    <input type="button" onclick="OpenTestImage(); return false;" value="Open Image and run tests"  />
 </td></tr>
  <tr><td style="width:200px; height:100%; border: 1px solid black; background-color:Silver">
    <cc1:WebThumbnailViewer ID="WebThumbnailViewer1" runat="server"
     Width="150px"
     Height="100%"
     TitleBar="Thumbs"
     ViewerID="WebImageViewer1"
     Centered="true" />
   </td><td style="width:100%; height:100%; border: 1px solid black; background-color:Silver">
   <cc1:WebImageViewer ID="WebImageViewer1" runat="server"
     Width="100%"
     Height="100%"
     AutoZoom="BestFit"
     AntialiasDisplay="ScaleToGray"
     TitleBar="Image" />
</td></tr>
   </table>
    </div>
<script language="javascript" type="text/javascript">
  var _testUrl = 'Images/DocCleanMultipage.tif';
  var _testIndex = 1;
  var _logger = null;
  
  // Runs once the page has finished loading
  atalaInitClientScript(Init);
  function Init(){
    InitEvents();
    InitTests();
  }
  
  // Bind our event handlers
  function InitEvents(){
    WebThumbnailViewer1.UrlChanged = UrlLoaded;
    WebThumbnailViewer1.SelectedIndexChanged = IndexChanged;
   
    // We use ImageSizeChanged because this event fires when
    // a callback is returned from the server, ImageChanged fires immediately
    WebImageViewer1.ImageSizeChanged = ImageLoaded;
  }
  
  // Set up the test functions
  function InitTests(){
    var testCase = new YAHOO.tool.TestCase({
     name: "Open Image Tests",
     testUrlLoaded : function(){
      // tests whether the url given is the one that was loaded in the WebThumbnailViewer
      YAHOO.util.Assert.areEqual(_testUrl, WebThumbnailViewer1.getUrl());
     },
    testSelectedIndex : function(){
      // tests that the index given is returned as being selected in the WebThumbnailViewer
     YAHOO.util.Assert.areEqual(_testIndex, WebThumbnailViewer1.getSelectedIndex());
    },
    testImageLoaded : function(){
      // tests whether the url given is the one that was loaded in the WebImageViewer
      YAHOO.util.Assert.areEqual(_testUrl, WebImageViewer1.getImageUrl());
   },
     testFrameIndex : function(){
     // tests that the given selected index was the index loaded into the WebImageViewer
      YAHOO.util.Assert.areEqual(_testIndex, WebImageViewer1.getFrameIndex());
    }
   });
   
 // Add the test case to the test runner
    YAHOO.tool.TestRunner.add(testCase);
   
    // Create the logger (shows it too)
    _logger = new YAHOO.tool.TestLogger("testLogger");
  }
  
  // Select a thumbnail once the image has been loaded into the thumbnailviewer
  function UrlLoaded(){
    WebThumbnailViewer1.SelectThumb(_testIndex);
  }
  
  // Change the test's selected index, so that clicking on a thumb will test that index
  function IndexChanged(){
    _testIndex = WebThumbnailViewer1.getSelectedIndex();
  }
  
  // We want to run the tests after the callback from the server
  // If this were an automated test, this should have a time limit
  // so we can run the tests even if there is no response from the server
  function ImageLoaded(){
    RunTests();
  }
  
  // Run the tests
  function RunTests(){
    YAHOO.tool.TestRunner.run();
  }
  
  // Open the test image
  function OpenTestImage(){
    WebThumbnailViewer1.OpenUrl(_testUrl);
  }
 </script>
</form>
</body>
</html>

Posted by David Cilley | 0 Comments

Ajax Image Sliders Part 3: Intervals with Opacity

This is Part 3 of a multi-part blog series.

  1. The OnDemand method
  2. The Interval method
  3. The Interval Opacity method

On the previous two slider examples, I used a YUI slider that had a range from -100 to 100, with a total of 201 possible values.  Both of these examples still have some disconnect from the action performed, and still feel like we're on the web.  I promised that we could improve upon this, and I don't mean by adding more intervals... there's one more browser trick we can use to get this right.

The Interval Opacity Method
This is possibly the most responsive method for using sliders with images in the native browser.  This method builds upon the Interval method by using browser native opacity to 'create' the missing steps between the intervals that we've already downloaded.  This also removes the OnDemand portion that was causing the flicker in Part 2.  Two image tags are shown in this method, one on top of the other, the the top one partially opaque depending on the percentage between intervals.

Without changing the server side code of Part 1, and making a few changes to the client side code that we've built in Part 2, we get a visual update for every available value of the slider.  As of this posting, this method works in IE 5.5/6/7, Mozilla Firefox, Safari, and Opera.

Here's the demo:

Here's what you need:

  1. An input tag to hold/edit the value (I used a text box)
  2. A JavaScript slider control (YUI slider in this example)
  3. Some JavaScript event handlers
  4. A server side method that returns an updated image from a querystring containing a path and a change value
  5. A loop that creates a series of img tags on page load, and pre-populates them with images (client side)
  6. A method that shows and hides the tags as they are needed (client side)
  7. A method that shows partially opaque images, using style.opacity or style.filter = alpha(value) for IE

The server side code for this example is attached the Part 1 article, I have provided the client-side code inline:

 <!-- YUI Dependencies -->
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yahoo-dom-event/yahoo-dom-event.js%22%3E%3C/script>
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/animation/animation-min.js%22%3E%3C/script>
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/dragdrop/dragdrop-min.js%22%3E%3C/script>
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/slider/slider-min.js%22%3E%3C/script>
 
 <!-- XHTML -->
 <div>
   <div id="container"><asp:Image ID="Image1" runat="server" ImageUrl="images/spacer.gif" Width="320" Height="240" /></div>
   <div style="padding-left:6px;">Gamma</div>
   <div id="sliderbg" style="position:relative; background:url(images/bg-fader.gif) 5px 0 no-repeat; height:28px; width:228px;">
    <div id="sliderthumb" style="position: absolute; top: 4px;"><img src="images/thumb-n.gif" /></div>
   </div>
  <input id="gammaVal" maxlength="4" size="4" type="text" value="0" style="position:relative; left:230px; top:-24px;" />
 </div>
 <script type="text/javascript">
var _slider;
var _gammaVal = document.getElementById('gammaVal');
var _url = 'Images/Rosebud.jpg'; // Starting image url
var _path = '<%= this.Page.Request.CurrentExecutionFilePath %>'; // url path of this page
 
var _imgs; // Array used to hold the img tags used for the images
var _steps = 20;
var _range = 200;
var _container = document.getElementById('container');
var _showing = 0;
	function init(){
_slider = YAHOO.widget.Slider.getHorizSlider("sliderbg", "sliderthumb", 0, _range);
_slider.subscribe('change', chgInterval); // Updates the text field and the image

_imgs = new Array();
// pre loads the images se we don't have to wait for them to load
for (var g = 0; g <= _steps ; g++){
var i = new Image();
i.src = _path + '?img=' + _url + '&gamma=' + (g * (_range / _steps));
i.style.left = '0px';
i.style.top = '0px';
i.style.position = 'absolute';
i.style.visibility = 'hidden';

_container.appendChild(i);
_imgs.push(i);
}
		YAHOO.util.Event.on(_gammaVal, "blur", checkValue);
YAHOO.util.Event.on(_gammaVal, "keydown", keyDown);
checkValue();
}

YAHOO.util.Event.onDOMReady(init);
	// Changes the text field value
function chgValue(x){
_gammaVal.value = x - 100;
}
	// hides the image at the given interval, and the image overlaying it (if any)
function hide(i){
_imgs[i].style.visibility = 'hidden';
		if (i + 1 < _imgs.length){
_imgs[i + 1].style.visibility = 'hidden';
}
}

// shows the image at the given interval, and the image overlaying it (if any)
function show(i){
_imgs[i].style.visibility = 'visible';

if (i + 1 < _imgs.length){
_imgs[i + 1].style.visibility = 'visible';
}
}
	// Changes the shown img tag
function chgInterval(x){
var shownum = Math.floor(x/(_range/_steps));
var opacity = Math.round(((x/(_range/_steps)) - shownum) * 100);
		if (shownum != _showing){
hide(_showing);
show(shownum);
_showing = shownum;
}

// set the opacity of the overlaying image, to a percentage
if (_showing + 1 < _imgs.length){
setOpacity(_imgs[_showing + 1], opacity);
}

setOpacity(_imgs[_showing], 100);
		chgValue(x);
}

// sets the percentage based opacity (0-100) of the given object
function setOpacity(obj, opacity){
obj.style.opacity = (opacity / 100);
obj.style.filter = 'alpha(opacity=' + opacity + ')';
}

// Checks the value of the text field, and sets the slider to that value
function checkValue(){
i = parseInt(_gammaVal.value);
		if (isNaN(i)){
i = _slider.getValue() - 100;
}

_slider.setValue(i + 100, false);
}

// Used to determine if the enter key has been pressed
function keyDown(k){
if (k.keyCode == 13){
checkValue();
return false;
}
}
</script>

Posted by David Cilley | 2 Comments
Filed under: , , ,

Ajax Image Sliders Part 2: Intervals with On Demand

This is Part 2 of a multi-part blog series.

  1. The OnDemand method
  2. The Interval method
  3. The Interval Opacity method

On the previous slider example, I used a YUI slider that had a range from -100 to 100.  This is a total of 201 different combinations for one image dialog, and that's about 10-20 times more requests than a web server should have to handle in a reasonable amount of time.  We want to make this look as if the slider is actually changing the image while we scroll it, but we don't want to request 201 images up front, and we don't want to load them all on demand either.

The Interval Method
This method requests a series of images from the server, at a set interval along the entire slider.  These images are requested as the page is loading, new requests are made when the slider has finished moving.  The pre-loaded images are stored in their own image tags, hidden and shown when needed.  This is along the same lines as many image pre-load scripts that were used for rollovers, many years ago.

Without changing the server side code of Part 1, I was able to use this method to create a slider that looks dynamic enough to fool most people into thinking it's changing it on the fly.  Because the main image tag is updated to hold the new image when the drag thumb is dropped, there can be a slight flicker.  We'll see if we can take care of that in Part 3. 

Here's the demo for this method:

Here's what you need:

  1. An img tag
  2. An input tag to hold/edit the value (I used a text box)
  3. A JavaScript slider control (YUI slider in this example)
  4. Some JavaScript event handlers
  5. A server side method that returns an updated image from a querystring containing a path and a change value
  6. A loop that creates a series of img tags on page load, and pre-populates them with images (client side)
  7. A method that shows and hides the tags as they are needed (client side)

The server side code for this example is attached the Part 1 article, I have provided the client-side code inline:

 <!-- YUI Dependencies -->  
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yahoo-dom-event/yahoo-dom-event.js%22%3E%3C/script>
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/animation/animation-min.js%22%3E%3C/script>
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/dragdrop/dragdrop-min.js%22%3E%3C/script>
 <script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/slider/slider-min.js%22%3E%3C/script>
 
 <!-- XHTML -->
 <div>
  <div id="container"><asp:Image ID="Image1" runat="server" ImageUrl="images/spacer.gif" Width="320" Height="240" /></div>
  <div style="padding-left:6px;">Gamma</div>
  <div id="sliderbg" style="position:relative; background:url(images/bg-fader.gif) 5px 0 no-repeat; height:28px; width:228px;">
   <div id="sliderthumb" style="position: absolute; top: 4px;"><img src="images/thumb-n.gif" /></div>
  </div>
  <input id="gammaVal" maxlength="4" size="3" type="text" value="0" style="position:relative; left:230px; top:-24px;" />
 </div>
<script type="text/javascript">
 var _slider;
 var _gammaVal = document.getElementById('gammaVal');
 var _img = document.getElementById('<%= this.Image1.ClientID %>');  // ASP.NET server control is used for server side access
 var _url = 'Images/Rosebud.jpg'; // Starting image url
 var _path = '<%= this.Page.Request.CurrentExecutionFilePath %>'; // url path of this page
 
 var _imgs; // Array used to hold the img tags used for the images
 var _steps = 20;
 var _range = 200;
 var _container = document.getElementById('container');
 var _showing = 0;
 function init(){
  _slider = YAHOO.widget.Slider.getHorizSlider("sliderbg", "sliderthumb", 0, _range);
  _slider.subscribe('change', chgInterval); // Updates the text field and the image
  _slider.subscribe('slideEnd', chgImg); // Updates the image
  
  _imgs = new Array();
  // pre loads the images se we don't have to wait for them to load
     for (var g = 0; g <= _steps ; g++){
   var i = new Image();
    i.src = _path + '?img=' + _url + '&gamma=' + (g * (_range / _steps));
    i.style.left = '0px'; 
    i.style.top = '0px';
    i.style.position = 'absolute';
    i.style.visibility = 'hidden';
   
   _container.appendChild(i);
   _imgs.push(i);
  }
        YAHOO.util.Event.on(_gammaVal, "blur", checkValue);
        YAHOO.util.Event.on(_gammaVal, "keydown", keyDown);
  checkValue();
 }
 
 YAHOO.util.Event.onDOMReady(init);
 // Changes the text field value
 function chgValue(x){
  _gammaVal.value = x - 100;
 }
 // Changes the url of the img tag
 function chgImg(){
  _img.src = _path + '?img=' + _url + '&gamma=' + (parseInt(_gammaVal.value) + 100);
  hideShowing();
 }
 
 function hideShowing(){
  _imgs[_showing].style.visibility = 'hidden';
 }
 // Changes the shown img tag
 function chgInterval(x){
  var shownum = Math.round(x/(_range/_steps));
  if (shownum != _showing){
   hideShowing();
   
   _showing = shownum;
   _imgs[_showing].style.visibility = 'visible';
  }
  chgValue(x);
 }
 
 // Checks the value of the text field, and sets the slider to that value
 function checkValue(){
  i = parseInt(_gammaVal.value);
  if (isNaN(i)){
   i = _slider.getValue() - 100;
  }
   
  _slider.setValue(i + 100, false);
 }
 
 // Used to determine if the enter key has been pressed
 function keyDown(k){
  if (k.keyCode == 13){
   checkValue();
   return false;
  }
 }
</script>
Posted by David Cilley | 6 Comments
Filed under: , , ,

AJAX Image Sliders: Part 1

This is Part 1 of a multi-part blog series.

  1. The OnDemand method
  2. The Interval method
  3. The Interval Opacity method

One of the most common dialogs in an image editor application is the slider with preview.  When you move these applications over to the Web, you end up losing some of the user experience because of the asynchronous nature of these apps.

There are several ways to use a JavaScript slider to change an image, and I will be covering at least 3 of them over the next few posts.

The "OnDemand" method:
This is probably the easiest to implement, but least user friendly.  I say it's not user friendly because the image only updates after the slider thumb has finished moving.  If we were to make the slider update for all points, it would innundate the server with requests.  The usage of a slider, however, is still an improvement over a postback or button/text field combination.  The feedback from the text box helps the user see that something is happening as well.

Here's a demonstration:

Here's what you need:

  1. An img tag
  2. An input tag to hold/edit the value(I used a text box)
  3. A JavaScript slider control (YUI slider in this example)
  4. Some JavaScript event handlers
  5. A server side method that returns an updated image from a querystring containing a path and a change value

The server side code for this example is attached to this article (if you need it), I have provided the client-side code inline:

<!-- YUI Dependencies -->  
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yahoo-dom-event/yahoo-dom-event.js%22%3E%3C/script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/animation/animation-min.js%22%3E%3C/script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/dragdrop/dragdrop-min.js%22%3E%3C/script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/slider/slider-min.js%22%3E%3C/script>
 
<!-- XHTML -->
<div>
  <div><asp:Image ID="Image1" runat="server" ImageUrl="images/spacer.gif" /></div>
  <div style="padding-left:6px;">Gamma</div>
  <div id="sliderbg" style="position:relative; background:url(images/bg-fader.gif) 5px 0 no-repeat; height:28px; width:228px;"> 
   <div id="sliderthumb" style="position: absolute; top: 4px;"><img src="images/thumb-n.gif" /></div>
  </div>
  <input id="gammaVal" maxlength="4" size="3" type="text" value="0" style="position:relative; left:230px; top:-24px;" />
</div>

<script type="text/javascript">
 var _slider;
 var _gammaVal = document.getElementById('gammaVal');
 var _img = document.getElementById('<%= this.Image1.ClientID %>');  // ASP.NET server control is used for server side access
 var _url = 'Images/Rosebud.jpg'; // Starting image url
 var _path = '<%= this.Page.Request.CurrentExecutionFilePath %>'; // url path of this page
 
 function init(){
  _slider = YAHOO.widget.Slider.getHorizSlider("sliderbg", "sliderthumb", 0, 200);
  _slider.subscribe('change', chgValue); // Updates the text field while the value is changing
  _slider.subscribe('slideEnd', chgImg); // We only want to change the image after we've finished sliding

        YAHOO.util.Event.on(_gammaVal, "blur", checkValue);
        YAHOO.util.Event.on(_gammaVal, "keydown", keyDown);
  checkValue();
 }
 
 YAHOO.util.Event.onDOMReady(init);
 // Changes the text field value
 function chgValue(x){
  _gammaVal.value = x - 100;
 }

 // Changes the url of the img tag
 function chgImg(){
  _img.src = _path + '?img=' + _url + '&gamma=' + (parseInt(_gammaVal.value) + 100);
 }
 
 // Checks the value of the text field, and sets the slider to that value
 function checkValue(){
  i = parseInt(_gammaVal.value);
  if (isNaN(i)){
   i = _slider.getValue() - 100;
  }
   
  _slider.setValue(i + 100, false);
 }
 
 // Used to determine if the enter key has been pressed
 function keyDown(k){
  if (k.keyCode == 13){
   checkValue();
   return false;
  }
 }

</script>
Posted by David Cilley | 4 Comments
Filed under: , , ,

Attachment(s): Slider_OnDemand.aspx.cs

Non-Rectangular Masks on the Web: Part 1

Over the past 15 or so years, I've edited and created a thousands of images.  I almost always use a mask for something, and it's very rarely only rectangular. I have a need for doing this on the web, natively in the browser, and I might not be the only one.

With JavaScript and the DOM, you can create a series rectangles that represent masks, but as the complexity of these masks go up, the number of DOM objects that need to be created go up.  This can make it slow to render, and slow to update.

Most of the browsers used today show PNG images with an alpha channel(transparency) correctly.  PNG images with an alpha channel can be used to mimic a client side mask by placing them on top of the object that you want to mask.  The image to the right is one of these PNG images, with a JPG image behind it. There are three things you need to do to create a masking PNG image:

  1. Create an image with a background color of your choice, at the size of the object you are masking.
  2. Make the areas you want selected completely transparent on the alpha channel (Black).
  3. Make the rest of the image half transparent on the alpha channel (Gray).

The remaining parts that you will need will be some JavaScript mouse events that allow you to drag a box on the object (something like a selection marquee), and a server side method that will create the PNG image described above.

Since I have access to DotImage, I can use the WebAnnotationViewer and RemoteInvoke to do everything.  In my case I decided to use a ReferencedImage annotation to hold the alpha PNG.  Here's a basic run down of the concept, using DotImage:

  1. Use the WebAnnotationViewer's Selection box to select an area on the image, and attach a JavaScript event to the Selection.Changed.  (MaskSelection In this example)
  2. In the Selection.Changed JavaScript event, use RemoteInvoke to run a Page level server side method that creates the masked PNG image from the selection box. (Remote_MaskSelection in this example)
  3. The server side method keeps two images in the disk cache.  One for the masked areas drawn, and one for the transparent PNG that represents those masked areas.
  4. Every time the server side method is called it overwrites these images with the new data from selection rectangle of the WebAnnotationViewer, effectively appending what was selected to the new mask.
  5. The mask image is an 8-bit grayscale which starts out completely gray (color index 127) to indicate half transparent.  Areas that are selected are drawn onto this image as black (index 0).
  6. The alpha PNG image is a 32-bit RGBA image with a background color (red in this case), and a SetAlphaFromMaskCommand is used to put the mask image into the alpha channel of this new PNG
  7. The alpha PNG is then added to an annotation on the server side, and the client side JavaScript is notified that it needs to update the annotation.

Here is the code I used to create the mask image and the alpha PNG:

 private void SynchronizeMaskAnnotation(Size size)
 {
   // This annotation was created on PageLoad, and is the only one on layer 0
   AnnotationUI ann = this.WebAnnotationViewer1.Annotations.GetAnnotation(0, -1, 0);
   
   ReferencedImageAnnotation refAnn = ann as ReferencedImageAnnotation;
   if (refAnn != null)
   {
     refAnn.Size = size;

     ReferencedImageData refAnnData = refAnn.Data as ReferencedImageData;
     if (refAnnData != null)
     {
       if (refAnnData.ImageObject != null)
       {
         ((Bitmap)refAnnData.ImageObject).Dispose();
         GC.Collect(); // This is done because the .NET Bitmap class doesn't free the file in time for us to write over it
       }
     }
   }

   this.WebAnnotationViewer1.UpdateAnnotations();
 }

 private void AppendMask()
 {
   string _pathToCache = System.Configuration.ConfigurationManager.AppSettings["AtalasoftWebControls_Cache"];
   string _prefix = Page.Session.SessionID + "_";
   string _pathToMask = this._pathToCache + _prefix + "Mask.png";
   string _pathToMaskAnn = this._pathToCache + _prefix + "MaskAnn.png";

   AtalaImage maskImage = null;
   AtalaImage alphaPNG = null;

   try
   {
     if (File.Exists(Page.MapPath(_pathToMask)))
       maskImage = new AtalaImage(Page.MapPath(_pathToMask));
     else
       maskImage = new AtalaImage(this.WebAnnotationViewer1.Image.Size.Width, this.WebAnnotationViewer1.Image.Size.Height, PixelFormat.Pixel8bppGrayscale, Color.Gray);

     alphaPNG = new AtalaImage(maskImage.Width, maskImage.Height, PixelFormat.Pixel32bppBgra, Color.Red);

     Canvas maskCanvas = new Canvas(maskImage);
     maskCanvas.DrawRectangle(this.WebAnnotationViewer1.Selection.Rectangle, new SolidFill(Color.Black));

     SetAlphaFromMaskCommand alpha = new SetAlphaFromMaskCommand(maskImage, false, AlphaMergeType.Replace);
     ImageResults results = alpha.Apply(alphaPNG);
     if (alphaPNG != results.Image)
     {
       alphaPNG.Dispose();
       alphaPNG = results.Image;
     }

     SynchronizeMaskAnnotation(alphaPNG.Size);

     maskImage.Save(Page.MapPath(_pathToMask), new PngEncoder(), null);
     alphaPNG.Save(Page.MapPath(_pathToMaskAnn), new PngEncoder(), null);
   }
   finally
   {
     if (maskImage != null)
       maskImage.Dispose();
     if (alphaPNG != null)
       alphaPNG.Dispose();
   }
 }
Posted by David Cilley | 1 Comments
Filed under: , ,

Atalasoft at AJAXWorld 2008 East

This year's AjaxWorld Conference & Expo was somewhat of a disappointment.  After experiencing the problems that I outlined last year, I anticipated a better overall conference.  They did appear to take a few things into consideration, such as the lunch breaks (this time you might have been able to sit down), and the communication of the free wireless access in the ballroom.  Very few of the sessions I attended were informative beyond scratching the surface of a particular topic or product.  I felt like this conference was still trying to sell me the idea of using AJAX, rather than show us what we (as an AJAX community) have been able accomplish since the last conference.  Here's a run down of the problems I saw with this conference, in frustration level order (1 being the most frustrating):

  1. This venue is too small.  Nearly every single session room was too small for the number of attendees that wanted to attend in any particular session (that I went to).  I missed three potentially interesting sessions because I couldn't even get into the room.  The sessions where I made it into a seat, still had anywhere from 5-20 people standing in the doorways or sitting on the floor.
  2. Wireless access was still not communicated, but we knew to ask about it this time.  There was no way to connect to the internet unless you were in the grand ballroom, most sessions were not in this room.
  3. If you didn't get to lunch or the snack break within the first 5-10 minutes, you might not get any food.
  4. Even though they improved the size of the dining area, there were many people standing and waiting for tables, even some sitting on the floor. 
  5. Most session slides were provided online, and not given to us beforehand on a cd (like last year).  Without wireless access, this was nearly useless.
  6. They did not enforce the badges at all, everything was open to everyone, regardless of what ticket you paid for. (except meals)

Sessions I attended:
Day 1:

  • Comet: The Web That's Instantly On and Always On by Jonas Jacobi (Kaazing): This was a fairly vague session on Comet.  Jonas went over the Bayeux protocol, which is used to do server push(s) to a client browser without plugins.
  • ASP.NET AJAX Design & Development Patterns by Joe Stagner (Microsoft): This was a great overview on ASP.NET and ASP.NET AJAX design patterns. Joe explained that the UpdatePanel is not always the best way to make an AJAX app, even though it is one of the easiest, because it sends the entire page state back and forth from the server.  He covered several models, two of which I found interesting: The Service Model, and the After Processing Model.  I recommend taking a look at his slides, which he has provided in his blog.
  • Improving ASP.NET User Interfaces with the AJAX Control Toolkit by Robert Boedigheimer (The Schwan Food Company): This was a good overview on the ASP.NET AJAX Control Toolkit.  He demonstrated several of the controls, but since there was no internet access, he couldn't demonstrate everything that he wanted to.

Day 2:

  • Can We Fix the Web? by Douglas Crockford (Yahoo!): This was the keynote I was looking forward to, Douglas usually has something good to say.  He discussed that security is the main problem with the web.  He outlined a three pronged approach to fixing it: 
    - Safe JavaScript subsets, such as ADsafe, Caja, and Cajita
    - Small browser improvements, like JSONRequest, and vats (where only part of the dom is accessible)
    - Massive browser improvements, like replacing JavaScript altogether with a new secure language, and creating a common text protocol to replace HTTP
    I agree that the Internet and it's components were not designed for many of the things we are doing with it today, but I don't believe that we need to start over.  The new and exciting applications that come out every day will push the technology toward where it needs to be.
  • An Introduction to the YUI Library by Eric Miraglia (Yahoo!): This session was a great look into YUI.  I've been using YUI in several of my demos, but I've only scratched the surface of what it's capable of.  Some good take-aways were the YUI compressor, css base and reset, YUI CSS Grid Builder, and the YUI profiler.
  • Enterprise Comet: Real-Time, Real-Time, or Real-Time Web 2.0? by Jonas Jacobi (Kaazing):  This was pretty much a condensed version of the previous presentation.  We found out that they aren't showing any examples or demos because they are in private beta right now.
  • jMaki as an AJAX Mashup Framework by Arun Gupta (Sun): This was an interesting concept.  jMaki is a level of abstraction that allows objects from different JavaScript libraries to interact with each other.  I'm not sure if we could use this in ASP.NET, but it's still a very cool concept.
  • Understanding the Top Web 2.0 Attack Vectors by Danny Allan (Watchfire): This was another session where there wasn't enough room for everyone.  We didn't bother waiting in the hallway, as we couldn't even hear the speaker.  I look forward to seeing this one on the DVD.
  • Writing Large Web Applications Using the YUI by Christian Heilmann (Yahoo!):  I had to stand outside in the hallway just to listen to the speaker on this one.  It sounded like a great session, but I couldn't take notes or see what he was going over.  Hopefully I'll be able to see it when the DVD comes out.
  • OpenAjax Gadgets & Widgets by Stewart Nickolas (IBM): This was my introduction to the OpenAjax Alliance.  They appear to have the same goal as jMaki, only this is an effort to get everyone to collaborate and contribute.  Stewart showed us a really slick mashup and gadget editor that was oriented toward developers.  I'm not sure where to find this editor, although I did find this.

Day 3:

  • Now Playing: Desktop Apps in the Browser! by Coach Wei & Bob Buffone (Nexaweb): This was another mildly entertaining ping-pong keynote from Nexaweb.
  • DreamFace: The Ultimate Framework for Creating Personalized Web 2.0 Mashups by Olivier Poupeney (DreamFace): I think this was the first session where the speaker actually put together a demo in front of us.  Not only was this refreshing, it was a very informative.  The framework is looks impressive.  I might try making a DreamFace gadget using some Atalasoft controls in the future.
  • The Digital Black Belt’s Guide to Building Secure ASP.NET AJAX Applications by Joe Stagner (Microsoft): This was the most informative and beneficial session that I went to these three days.  Some great take-aways were WebScarab, Internet Explorer 8's DOM explorer, ViewState Decoders, and ConTEXT.  His slides can be found here.

 

Posted by David Cilley | 5 Comments
Filed under: , , ,

Passing DOM elements from one frame to another

I came across an interesting problem with Internet Explorer and Safari this week:

I wanted to take an arbitrary html element that had been created on a page, and send it to an iframe that was on the same page.

I created a simple test page with an iframe, a div tag, and a button.  After a few lines of JavaScript, I had it working in FireFox.  When I tested it in IE 7, it gave me an "Invalid argument" exception, and didn't specify what was invalid.  This doesn't work in Safari either.  Opera handles it exactly like FireFox.

After tinkering with it for a while, I thought it might have something to do with the ownerDocument property on the DOM element. I thought that if I could change that property, that I may be able to get it to work.  You can't change this property directly... so I thought maybe by removing the object from the parent object, it would allow me to append it to another document.  This doesn't work either.

Everything that I tried ended up not working, so as a last resort, I decided to create a copy of the element on the child frame.  This works for me, but could be missing some attributes that may be important to someone else.

I made a test page that allows you to test both the direct appendChild method, and the copy code below, while logging all errors that occur.  You can see that this test page works in both IE 7 and Safari when the checkbox is checked.

Here is the code that I used to copy the element on the child frame's document:

// o : DOM object to copy
// childDoc : document object from the child frame
function CopyToChildObject(o, childDoc){
   // create a new object on the other document
   var n = childDoc.createElement(o.tagName);
  
   // copy everything inside this tag
   n.innerHTML = o.innerHTML;
  
   // copy attributes
   for (var i in o){
      try{
         n.setAttribute(i, o.getAttribute(i));
      }
      catch (e){
         // do nothing with errors
      }
   }

   // copy style  
   for (var i in o.style){
      try{
         n.style[i] = o.style[i];
      }
      catch (e){
          // do nothing with errors
      }
   }
  
   return n;
}
Posted by David Cilley | 1 Comments
Filed under: ,

Using YUI Animation to slide AJAX thumbnails in and out

One of the easiest usability improvements that can be made to an imaging application, is the ability to hide the thumbnails from view when they are no longer needed.  Most users would prefer to see as much of the image, and as little of the application on the screen as possible.

Using YUI's Animation Utility, you can accomplish this task, while adding a little more feedback that something is happening.

The thumbnails and the main viewer are placed in separate table cells, so that the whole table can be fit to the width of the browser (a common requirement for imaging apps).  The object that will be used for the animation, is a div tag (slideAnim in this case) that holds the thumbnails, and clips the content if it's too large. Keep in mind that height and width must be defined on all objects up the tree, if percentage based values need to be used.

Here's how I did it:

<!-- YUI Dependencies -->
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.0/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.0/build/animation/animation-min.js"></script>

<table style="width:100%;height:630px;">
<tr>
   <td valign="top">
      <div id="slideAnim" style="overflow:hidden; width:202px; height:630px;">
         <cc1:webthumbnailviewer id="WebThumbnailViewer1" runat="server" width="200px" height="600px" viewerid="WebImageViewer1" thumbsize="160, 120"></cc1:webthumbnailviewer>
      </div>
   </td>
   <td style="width:100%;" valign="top">
      <cc1:webimageviewer id="WebImageViewer1" runat="server" height="600px" width="100%" />
   </td>
</tr>
</table>       

<button id="demo-run" onclick="return false;">SlideIn/Out</button>

<script type="text/javascript">
   var _vis = true;
   var _outAttributes = { 
       width: { to: 0 } 
   };

   var _inAttributes = { 
       width: { to: 202 } 
   };
    
   // create animation objects
   var _animOut = new YAHOO.util.Anim('slideAnim', _outAttributes, 1, YAHOO.util.Easing.easeOut); 
   var _animIn = new YAHOO.util.Anim('slideAnim', _inAttributes, 1, YAHOO.util.Easing.easeIn); 
    
   // bind events
   YAHOO.util.Event.on('demo-run', 'click', slide);
   _animOut.onComplete.subscribe(onSlideOut);
   _animIn.onComplete.subscribe(onSlideIn);
    
   function onSlideOut(){
      _vis = false;
   }   

   function onSlideIn(){
      _vis = true;
   }    
    
   function slide(){
      if (_vis){
         slideOut();
      }
      else{
         slideIn();
      }
  
      return false;
   }
    
   function slideOut(){
      _animOut.animate();
   }

   function slideIn(){
      _animIn.animate();
   }
</script>

Slick Ajax Magnifier

magnifier screenshotRecently we had a customer ask if we had anything like our WinForms Magnifier MouseTool available on the WebForms side of things.  We get this question somewhat frequently, and it's always one of those features that seems to fall off the end of the development cycle to higher priority features.  The answer to this question has always been: "It's not inherantly supported, but you could probably make your own by floating another WebImageViewer over the mouse position."

I spent a little of my free time on it, and I've been able to confirm that this works.  Here are the steps that you need to take to get it to work:

  1. Create a WebForm with two WebImageViewer controls on it, one as the main viewer, and one as the magnifier.
  2. Wrap the magnifier WebImageViewer in a div tag, give the div tag an ID, and set the style to display:none.
  3. Attach a JavaScript MouseDownLeft event to the main viewer.
  4. In the MouseDownLeft event, attach JavaScript MouseMove and MouseUp events to both of the viewers, and set the style of the magnifier div to display:none.
  5. In the MouseMove events, set the position of the magnifier div to the position of the mouse - half the size of the magnifier.
  6. In the MouseUp event, detach the MouseMove and MouseUp events (by setting them to a blank function) and set the magnifier div style to display:none.
  7. The final step is to load the same image into both of the WebImageViewer controls, BestFit the main viewer, and keep the magnifier at zoom level 1.

I have attached a VS 2005 demo solution that shows how all of this is put together. This is a file system based web application with dll.refresh files pointing to the install location of DotImage.  If you do not have DotImage installed at that location, you will need to remove and re-add your references to the Atalasoft dll's.

Posted by David Cilley | 5 Comments
Filed under: , ,

Attachment(s): MagnifierDemo.zip

Presentation: Introduction to AJAX in ASP.NET

I'd like to thank everyone that attended my presentation on AJAX for the Western Mass .NET Users Group last night.  I enjoyed presenting to the group and speaking with most of you.

Topics that were covered were:

  • What is AJAX?
  • AJAX in .NET with IFrames
  • ASP.NET AJAX Library
  • ASP.NET AJAX Extensions
  • ASP.NET AJAX Toolkit
  • AJAX Annotations Demo
  • Hero & Villain Card Builder

If you have any questions about the material covered, please don't hesitate to contact me. I'd be happy to answer any questions you might have.

For reference, I have attached my slides, enjoy! (Visual Studio 2005 and ASP.NET AJAX Extensions required)

Posted by David Cilley | 0 Comments
Filed under: , ,

Attachment(s): AJAXIntro.zip

Atalasoft at AjaxWorld NYC 2007

Lou and I recently returned from the AjaxWorld Conference & Expo in NYC.  Most of the sessions I attended were very informative, some were advertorials, some were advertisements, and a few were actually laughable.  This AjaxWorld conference was not run as well as the last one I attended.  The biggest problems I saw with this conference were:

  1. Lack of communication for wireless access. Last year they had the login info on the main presentation slides before every session... this would have been a great idea.  We didn't even know that they offered it until the second day.
  2. Wireless access quality was minimal, and non existent in several of the session rooms.
  3. Session rooms were way too small in most cases, I had to sit on the floor for several of the sessions.
  4. Projector screens were usually in the corner, making it nearly impossible for anyone after the 3rd or 4th row to follow along.
  5. At least one of the presenters I was interested in didn't show up, and we weren't notified until after all the other sessions had started. 
  6. All session slides were provided on an unmarked cd-r (which I discovered when I got back to Atalasoft)  I had assumed that it was a demo or something, like all of the other cd's provided.
  7. They did not appear to enforce any of the badges, everything was open to everyone. (except meals)

Sessions I attended:
Day 1:

  • AJAX, the Browser Application Platform by Douglas Crockford (Yahoo!): Douglas gave a very well thought out keynote to start off this conference.  He basically described how we got to where we are now with AJAX, and discussed several of the failed attempts at making the web more interactive (like Java Applets).  He also explained what's driving this technology, pointed out several flaws with current technologies, and gave a great overview on where we're currently at with AJAX.
  • JavaScript Performance: Speeding up Your AJAX Apps by Ryan Stout:  This session was by far, the most beneficial topic that I went to.  Ryan gave an in depth view of several things you can do to speed up your JavaScript.  A few important points that he brought up were profiling with FireBug, avoiding page re-flows, using setTimeOut on long operations to allow the browser time to breathe, compressing js files with gzip, and setting expiration dates on js files to make sure that they are cached.  You should take a look at his slides if you are writing any performance hungry JavaScript.
  • Enterprise Web 2.0 - Programming with Levers, Dials and maybe Switches by Coach Wei & Bob Buffone (Nexaweb): This ping-pong keynote compared using Nexaweb for Web 2.0 in enterprise to using a lever.  They had an interesting demo that transparently switched from thin client to thick client when the amount of data increased to more than the thin client could handle.  It's an interesting concept, but the demo that I saw didn't show the scroll bars correctly, and didn't seem to maintain state when it switched clients.  They promised that it was easy as a lever, but they never told us exactly how easy it really was.

Day 2:

  • Scaling AJAX: The Promise and the Challenge of Modern Web Development by Bret Taylor (Google):  This was an extremely informative keynote.  Bret described many of the hurdles that were encountered during the development of AJAX apps like Google Maps.  He also went into current projects, and predictions on what the future holds for AJAX.
  • The User Is the Killer App. Empower Them! by Luis Derechin (JackBe)
  • Google Gadgets and Componentized Websites by Adam Sah (Google): This session was very well done.  Adam gave an in depth view of Google Gadgets, how they were made, and how people are using them.  Basically speaking, the gadgets are IFrames of content offered by just about anyone.  Many of the gadgets in Google's Gadget library are provided by outside developers.  This really got me thinking about what could be useful inside a Google Gadget, and how I could help the gadget community.
  • Q&A session with Google by Bret Taylor, Adam Sah, Scott Blum
  • The Face of Enterprise 2.0 by Ric Smith (Oracle):  This keynote was canceled by the presenter because his laptop black screened.
  • Delivering Data To Your AJAX Solutions by Bob Zurek
  • Web Vector Graphics & Dojo: Draw This! by Dylan Schiemann: This was an overview of vector based graphics using the Dojo.gfx toolkit.  It looked like it's easier to use Dojo, instead of native browser offerings such as VML and canvas, as they wrap all of the functionality into one framework.
  • Microsoft AJAX Library Architecture – A Deep Dive by Nima Dilmaghani (Microsoft):  I had been looking forward to this session the whole day.  Nima gave a quick overview of the Microsoft AJAX Library, and emphasized that the library could be used on other platforms besides ASP.NET.  This session lost steam quickly as the presenter appeared to be unprepared.  He did offer some useful links at the end of the presentation.

Day 3:

  • AJAX in the Balance by Brad Abrams (Microsoft): This was one of the main keynotes I was interested in.  Brad showed us a demo web site that he created to sell dice.  He used ASP.NET AJAX to stop the full page refresh, and add autocomplete to the search box.  He also showed us how that demo worked with PHP and Linux, on a Mac.  He gave us a preview of JavaScript intellisense in Orcas (which looks really sweet btw).  The 'wow' part of the keynote was when he showed off a WPFE demo that had turning pages, music, and video.  Very impressive.
  • 'HDUX' – High Definition User Experience with Flex & Apollo by Christophe Coenraets (Adobe):  This keynote was another impressive one that wowed the audience with an Ebay on the desktop using Apollo. He also showed another page turning demo with biological transparancies as the subject matter.  This was just as impressive as the previous keynote's demo.
  • Step by Step - Helmi Open Source RIA Platform by Juho Risku (Helmi): The initial speaker was pretty good, it was the second presenter that appeared to be too nervous.  He couldn't speak loud enough for everyone to hear.  He also didn't explain what he was doing while he was typing out his demo and he couldn't get his demo to work until the very end of the presentation.  I think about half of the total time was spent in silence.
  • JavaScript Puzzlers - Is An IDE the Fifth Wheel or the Sixth Sense by Mike Aizatsky (JetBrains):  This keynote was one of the most entertaining ones. Mike started out by handing out a quiz with a bunch of JavaScript puzzles.  These puzzles were focused on quirks of the JavaScript language, and were very informative.  He included the audience in discussion of quirks like the difference between == and ===.  The hardest puzzle that he offered was number 4:
    What will be displayed in each case?
    alert("0" == 0);
    alert("1" == 1);
    alert("0x10" == 0x10);
    alert("010" == 010);
  • JSON: Making the 'X' in AJAX Superfluous by Douglas Crockford (Yahoo!): This was another great talk by Douglas.  He offered a broad overview of JSON, how it worked, and why you should use it for data transfer.  The Q&A session at the end got a little out of hand, as some of the attendees asked questions that Douglas had nothing to do with (such as development of Internet Explorer)
  • Performance-Tune Your AJAX Application by Bob Buffone (Nexaweb):  This was a fairly informative session on JavaScript performance.  Some key topics Bob covered were using Dojo to combine JavaScript files on the fly, and profiling in browsers besides FireFox.
Posted by David Cilley | 4 Comments

IE7 DXImageTransform and PNG transparency problem

Recently I was tasked with creating a spiffy demo for our new AJAX Annotations control and I ran across an interesting problem. For reference, I am creating a demo that looks and behaves much like the AJAX Multipage Tiff Demo, with all the transparent PNG goodness that we can now take advantage of in IE7.

Since the default doctype in VS 2005 is XHTML 1.0 Transitional, I decided now is a good time for me see how easy or difficult using this doctype can be.  Primarily I have been sticking to HTML 4.0, because it's a heck of a lot easier to get your nested tables fitting to percentage based height and width.

Switching to this doctype caused a few minor problems, but the biggest one that I noticed was this:

What you are seeing here is the bottom right corner of the image I am attempting to use like a modal dialog background (the demo mentioned above shows how this is used).  This image is originally 150x150 pixels, and has been stretched to fit the browser's available area.

It's supposed to look like this:

So now I bet you're wondering the same thing I was.  What am I doing wrong on the page that's causing this?  The first thing I thought, was the doctype change.  This didn't fix the problem.  Then I figured that I must be using the old IE transparent PNG hack that's I've covered in previous posts.  Nope, not using it in this app.  However, I did find a DXImageTransform filter in one of the underlying IFrames.  Once I got rid of the DXImageTransform, the problem went away.

In my tests, regardless of doctype, this problem can show up if you are using any DXImageTransform filters on any object on any of the frames currently loaded into the browser window.  It seems to show up less if the PNG images are not overlapping any other objects, although I haven't been able to pinpoint exactly which cases are ok.

I have created a test html page that demonstrates this problem.  When you first load the page, the PNG image shows up correctly.  Once you click one of the buttons (using IE7 and Windows XP) the PNG image becomes blurred on the bottom and the right of the image.