Photo Gallery – ASP.NET + JavaScript
I will try to wrap up phase 1 of the roadmap for the photo gallery project. During this article I will try to pull together any loose ends.
JavaScript – Utilities
One of the goals of this project is to allow me to dabble in technologies that I typically don’t use during the course of the development I do at my day job. JavaScript is one of those areas. The amount of JavaScript that is in this project is minimal. However, it is an opportunity to extend my self. I should also mention that I am leveraging the book “Pro JavaScript Techniques” by John Resig as a reference.
I want do my part in preventing my JavaScript from breaking existing code. I placed my re-usable JavaScript code in a namespace called “rlc”…sorry if you have the same initials. To create a new namespace simply do the following:
// Create my namespace. var rlc = {};
I have a number of public JavaScript methods that belong to this namespace and are shown in the following snippet:
// Find all elements that have a certain class name. rlc.hasClass = function( name, type ) { var r = []; var re = new RegExp("(^|\s)" + name + "(\s|$)"); var e = document.getElementsByTagName( type || "*" ); for( var j=0;j<e.length;j++) { if(re.test(e[j].getAttribute("class"))) { r.push(e[j]); } } return r; } // Set the class name. rlc.setClass = function( elem, className ) { if(elem.className != null) { elem.className = className; } else { alert('setClass is going to fail!'); } } // Fade an element in. rlc.fadeIn = function( elem, timeInMilliSeconds, numberOfSteps, maxOpacity, callback ) { // Set the time in seconds if(timeInMilliSeconds == undefined) { timeInMilliSeconds = 1000; } // Set the number of steps if(numberOfSteps == undefined) { numberOfSteps = 20; } // Set the maximum opacity if(maxOpacity == undefined) { maxOpacity = 100; } var opacityStep = maxOpacity / numberOfSteps; var timerStep = timeInMilliSeconds / numberOfSteps; // Start the opacity at 0 rlc.setOpacity( elem, 0 ); // Show the element (but you can't see it, since the opacity is 0) rlc.show (elem); // Step the opacity enough to reach the maximum opacity var opacityValue = 0; var step = 0; while(opacityValue < maxOpacity) { // A closure to make sure that we have the right // opacity values when the timer callback is // triggered. // (function(){ opacityValue = opacityValue + opacityStep; if(opacityValue > maxOpacity) { opacityValue = maxOpacity; } step = step + 1; var opacity = parseInt(opacityValue); var timer = parseInt(step * timerStep); // Set the timeout to occur at the specified time in the future setTimeout( function() { // Set the new opacity of the element rlc.setOpacity( elem, opacity ); if(opacity == maxOpacity && callback!=undefined) { callback(); } } , timer ); })(); } } // Fade and element out. rlc.fadeOut = function( elem, timeInMilliSeconds, numberOfSteps, callback ) { // Set the time in seconds if(timeInMilliSeconds == undefined) { timeInMilliSeconds = 1000; } // Set the number of steps if(numberOfSteps == undefined) { numberOfSteps = 20; } var opacityStep = 100 / numberOfSteps; var timerStep = timeInMilliSeconds / numberOfSteps; // Step the opacity enough to reach the maximum opacity var opacityValue = 100; var step = 0; while(opacityValue > 0) { // A closure to make sure that we have the right // opacity values when the timer callback is // triggered. // (function(){ opacityValue = opacityValue - opacityStep; if(opacityValue < 0) { opacityValue = 0; } step = step + 1; var opacity = parseInt(opacityValue); var timer = parseInt(step * timerStep); // Set the timeout to occur at the specified time in the future setTimeout( function() { // Set the new opacity of the element rlc.setOpacity( elem, opacity ); if(opacity==0 && callback!=undefined) { callback(); } } , timer ); })(); } } // Set an opacity level for an element // (where level is a number 0-100) rlc.setOpacity = function(elem, level ) { // If filters exist, then this is IE, so set the Alpha filter if(elem.filters) { elem.style.filter = 'alpha(opacity=' + level + ')'; } else { elem.style.opacity = level / 100; } } // A function for showing (using display) an element rlc.show = function(elem) { // Set the display property back to what it use to be, or use // 'block', if no previous display had been saved elem.style.display = elem.$oldDisplay || ''; } // A function for hiding (using display) an element rlc.hide = function(elem) { // Find out what its current display state is var curDisplay = getStyle( elem, 'display' ); // Remember its display state for later if(curDisplay != 'none') { elem.$oldDisplay = curDisplay; } // Set the display to none (hiding the element) elem.style.display = 'none'; } // Get the html element by id rlc.getById = function( id ) { return document.getElementById( id ); }
- getById – Is just a helper method to hide the typographically more complex call.
- show & hide – Show or hide an element by setting it’s display attribute to either ‘none’ or the original value. Note that during hiding the original value is saved.
- setOpacity – Is used to set the opacity of an element. The complexity in this method is caused by not all browsers implementing opacity in the same way.
- fadeIn & fadeOut – These two functions allow a smooth fading elements. I expanded on Resig’s work a bit to allow a callback function to be triggered when the fading is complete.
- setClass – I use this to set the ‘class’ attribute of an html element.
- hasClass – I use this to return all the elements that have their ‘class’ attribute set to a certain type.
JavaScript – Thumbnail Images
JavaScript is used in the following ways to improve the user experience on the film strip control.
- As soon as possible, the thumbnail images fade in from fully transparent to 67% opaque.
- As you mouse over a thumbnail, it’s opacity is changed to 100%. And as you mouse out, the opacity is changed back to the 67% level. This gives the appearance that you can select the thumbnail.
- After you click a thumbnail, the thumbnail’s opacity is changed to 100% and a black border replaces the normal white border. This gives the appearance that the thumbnail is selected.
This is achieved with the following ASP.NET and JavaScript:
<div class="outerFrame"> <div class="imageFrame" id="_imageFrame" runat="server"> <div id="_fade" runat="server" style="display:none;"> <asp:LinkButton ID="_link" runat="server"> <img class="picture" id="_image" runat="server" alt="Loading..." /> </asp:LinkButton> </div> </div> </div> <script type='text/javascript' language='javascript'> // Fade in the element as soon as possible. var elemId = '<%= this._fade.ClientID %>'; var elem = rlc.getById( elemId ); if(elem != null) { rlc.fadeIn( elem, 1000, 30, 67 ); } // Hook up mouse event handlers. elem.onmouseover = function(){ rlc.setOpacity( this, 100 ); } elem.onmouseout = function() { // Am I the selected frame? var fadeOut = true; var sel = rlc.hasClass("imageFrameSelected"); if( sel.length > 0 ) { var imageFrame = sel[0]; for(var i=0;i<imageFrame.childNodes.length;i++) { if(imageFrame.childNodes[i].id==this.id) { fadeOut = false; break; } } } if(fadeOut == true) { // I must not be the selected frame... rlc.setOpacity( this, 67 ); } } elem.onclick = function() { var linkId = '<%= this._link.ClientID %>'; var link = rlc.getById(linkId); var href = link.href; if(href!=null && href.indexOf('doPostBack')!=-1) { // This is a thumbnail of an image. var sel = rlc.hasClass("imageFrameSelected"); if( sel.length > 0 ) { // There was a previously selecte image. // Reset its class. rlc.setClass( sel[0], "imageFrame"); // Reset the fade on the previously selected // child node. for(var i=0;i<sel[0].childNodes.length;i++) { var node = sel[0].childNodes[i]; if(node.id != undefined) { if(sel[0].childNodes[i].id.indexOf('_fade')!=-1) { rlc.setOpacity(sel[0].childNodes[i], 67); } } } } // Set the opacity of the selected node. rlc.setOpacity( this, 100 ); // Set me as the selected thumbnail. var imageFrameId = '<%= this._imageFrame.ClientID %>'; var frame = rlc.getById(imageFrameId); rlc.setClass( frame, "imageFrameSelected"); // Gather information to call the // global 'showNewImage' method. var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); var thumbUrl = image.src; var temp = new Array(); temp = thumbUrl.split('&'); var imageUrl = temp[0]; for(var i=1;i<temp.length;i++) { if(temp[i]!='t=1') { imageUrl += '&' + temp[i]; } } var imageName = image.alt; showNewImage(imageUrl, imageName); // Return false to short-circuit the normal // post back. return false; } else { // This is the thumbnail of a directory. // Returning true allows the normal postback // to occur. return true; } } </script>
JavaScript – Default Page
JavaScript is also used the main page to minimize the number of post-backs required. The following JavaScript is on the main page:
<asp:ScriptManager ID="ScriptManager" runat="server" EnablePageMethods="true"> <Scripts> <asp:ScriptReference Path="~/script/jsUtils.js" /> </Scripts> <Services> <asp:ServiceReference Path="~/ServiceLayer.asmx" /> </Services> </asp:ScriptManager> <script language="javascript" type="text/javascript"> // Show the new image function showNewImage( imageUrl, imageName ) { // Get the image element. var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); // Fade out the current image. rlc.fadeOut(image, 500, 30, function(){ fadeInNew( image, imageUrl, imageName ); }); return false; } function fadeInNew(image, imageUrl, imageName) { // Set the new image source image.src = imageUrl; image.alt = imageName; image.onload = function() { rlc.fadeIn(image, 500, 30, 100); }; // Connect up the save link var saveId = '<%= this._aSave.ClientID %>'; var saveLink = rlc.getById(saveId); saveLink.href = imageUrl + "&d=1"; // Show the image name. var titleId = '<%= this._title.ClientID %>'; var title = rlc.getById(titleId); if(title != null) { title.value = imageName; } UpdateImageInfo(imageUrl); return false; } // Set the privacy - public function SetPrivacyPublic() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); Rlc.PhotoGallery.Web.ServiceLayer.SetPrivacyPublic(image.src, OnSucceedAdmin, OnFailed); return false; } // Set the privacy - family function SetPrivacyFamily() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); Rlc.PhotoGallery.Web.ServiceLayer.SetPrivacyFamily(image.src, OnSucceedAdmin, OnFailed); return false; } // Set the privacy - private function SetPrivacyPrivate() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); Rlc.PhotoGallery.Web.ServiceLayer.SetPrivacyPrivate(image.src, OnSucceedAdmin, OnFailed); return false; } // Rotate image clockwise function RotateCW() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); Rlc.PhotoGallery.Web.ServiceLayer.RotateCW(image.src, OnSucceedAdmin, OnFailed); return false; } // Rotate image counter-clockwise function RotateCCW() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); Rlc.PhotoGallery.Web.ServiceLayer.RotateCCW(image.src, OnSucceedAdmin, OnFailed); return false; } // Set the image title function SetTitle() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); var titleId = '<%= this._title.ClientID %>'; var title = rlc.getById(titleId); Rlc.PhotoGallery.Web.ServiceLayer.SetTitle(image.src, title.value, OnSucceedAdmin, OnFailed); } // Set image tags function SetTags() { var imageId = '<%= this._image.ClientID %>'; var image = rlc.getById(imageId); var tagsId = '<%= this._tags.ClientID %>'; var tags = rlc.getById(tagsId); Rlc.PhotoGallery.Web.ServiceLayer.SetTags(image.src, tags.value, OnSucceedAdmin, OnFailed); } // Update the image info function UpdateImageInfo(imageUrl) { Rlc.PhotoGallery.Web.ServiceLayer.GetImageInfo(imageUrl, OnSucceedAdmin, OnFailed); } // Success callback function OnSucceedAdmin(value) { var infoId = '<%= this._imageInfo.ClientID %>'; var label = rlc.getById(infoId); label.innerHTML = value; var temp = new Array(); temp = value.split('['); if(temp.length>2) { var rhs = temp[1]; temp = rhs.split(']'); if(temp.length>2) { var tagsId = '<%= this._tags.ClientID %>'; var tags = rlc.getById(tagsId); if(tags!=null) { tags.value = temp[0]; } } } } // Fail callback function OnFailed(error) { alert(error.get_message()); } </script>
Notice the ScriptManager is configured to load my JavaScript utilites. It is also configured to create the JavaScript hooks to allow calls to a service layer that I created to expose the underlying business logic layer and data objects (more on this later).
When ever a thumbnail is clicked, the ‘showNewImage’ method is eventually called. This method fades out the current image, using a callback to the ‘fadeInNew’ method. The ‘fadeInNew’ method then sets the new image source, connects up a few links, and fades in the new image.
The ‘SetPrivacyPublic’, ‘SetPrivacyFamily’, ‘SetPrivacyPrivate’, ‘RotateCW’, ‘RotateCCW’, ‘SetTitle’ and ‘SetTags’ methods use JavaScript to call web services that have been exposed to manipulate business layer objects. This allows those objects to be updated without the need for a full post back.
Soon, I will try to post the full project in downloadable form. I will also try to setup a workable example.