Tag Archives: jQuery

Caching Dom Objects with jQuery

UPDATE

The technique in this post is incorrect and does not work. I failed to realize that jQuery’s selector method (i.e. $() ) always returns a jQuery object. If the element is not in the dom, the jQuery object will be an empty set. But a set nonetheless and not null. Thus, it will never coalesce in the way I intended. I wondered why no-one else was doing this. It seems I have my answer.


I learnt something interesting about JavaScript that I was not aware of this last week. I’ll elaborate by walking you through what I discovered. Basically, I was trying to come up with a pattern that stored a jQuery object, and if for some reason that object had become null at some point, then a failsafe re-query would occur. It looked like this:

  1. Declare a variable
  2. Add it to the pageObjects object, with a cascade to the jQuery selection call
  3. Prime the variable in the document.ready function
  4. All dom manipulations are via the pageObjects object
var someDiv; // step 1

// step 2
var pageObjects = {
  someDivOnPage: someDiv || $('#someDiv'), // different name not necessary, but illustrative
  otherProperty: // ... pageObjects has many properties 
};

//********* document.ready function ********* / 
$(function() {
    // step 3
    someDiv = $('#someDiv');
});

//  ... access object later in script ...
// step 4
pageObjects.someDivOnPage.css('color', #eeeeee);

I discovered, after a while, that this did not work. If that div in one operation was removed from the dom, and re-added in another operation, calling pageObjects.someDivOnPage was undefined. Apparently, the coalesce in that statement was a one-off. That is, the first time pageObjects.someDivOnPage is accessed, the statement coalesces to the jQuery query as planned. But that statement only runs once and the property of that object is stored in a memory location. Once the element underpinning that jQuery object is removed from the dom, that property no longer points to that jQuery object and it doesn’t coalesce to the re-query as it did upon first access.

The proper way to do it is as follows:

var someDiv;

var pageObjects = {
  someDivOnPage: function () {
	return someDiv || $('#someDiv'); // different name not necessary, but illustrative
  },
  otherProperty: // ... pageObjects has many properties 
};

//********* document.ready function ********* / 
$(function() {
    someDiv = $('#someDiv');
});

//  ... access object later in script ...
pageObjects.someDivOnPage().css('color', #eeeeee);

As you can see, I have changed the property in the pageObjects object to be a function. And later on, when I reference that property I call it as a function.

So, the conclusion is that when the interpreter first parses the pageObjects object, the properties are assigned in the way that an expression is executed. To use pseudo code, it would look like this:

// for my first flawed approach.
... {
  someDivOnPage = someDiv || $('#someDiv');
};

// for my corrected approach
... {
  someDivOnPage = function () {
	return someDiv || $('#someDiv'); // different name not necessary, but illustrative
  };
};

That assignment occurs once and the result of that expression is assigned to the property. Makes complete sense really. It’s just something I had not picked up on. Felt like a goose.

So you’re probably wondering about performance? Well, that was the whole idea. The project which I am working on had jQuery performing queries heaps of times for the same dom objects in different operations during the lifecycle of the page (button-clicks, mouse-overs etc.).

If you look at this benchmark which I created at jsperf.com, you will see that this approach is very performant.

The other advantage is that if you are using a good IDE, you get intellisense when you “dot through” to the relevant property on the pageObjects object. This makes maintenance a lot easier and reduces the opportunity for typos when writing out each and every jQuery selector.

jQuery Ui DatePicker with ASP.NET MVC

On a project last year I had to use the jQuery Ui datepicker with ASP.NET MVC 3. I always intended to record the steps with explanations when I got a chance. And last week, I managed to find the time to do this:

One thing that I did in the implementation was include a colon in the DisplayAttribute of my model

        [Display(Name="Date of Birth: ")]
        public DateTime DateOfBirth { get; set; }

That leads to the non-optimal scenario whereby a colon is showing up in validation. In the video, I demonstrated how you could fix this in my client-side validation. But I forgot to demonstrate how you could fix it in the server-side validation (on the off-chance that your users have JavaScript disabled). You can use the following code in the action method of the controller:

            if (!ModelState.IsValid)
            {
                //  HACK: This will remove the colon for server-side validation. I recommend not including a colon in the
                //  Display attribute on the relevant member in your model. Just put the colon in the razor view.
                ModelState["DateOfBirth"].Errors.Clear();
                ModelState["DateOfBirth"].Errors.Add(string.Format("The value '{0}' is not a valid for Date of Birth.", Request.Form["DateOfBirth"]));
            }

As I mention in my comment, I believe that a colon should not be included in the model and could easily be added to the razor View. That would totally obviate the need for that hack.

Get the code:

Updating jQuery Using Nuget to a Version Less than 2.0

jQuery 2 is out and whilst I’m keen to embrace new things, a lot of 3rd party libraries have jQuery 1.* as a dependency. If you just update jQuery using Nuget, it brings down jQuery 2.*, which may not be what you want.

So, the easiest way to target a specific version is to using the following command with the Package Manager Console (I chose 1.10.1 as an example):
PM> Install-Package jQuery -Version 1.10.1

Bus Antics

I have been playing around with the animation framework in jQuery and thought I would use it to tell a story about what happened on a bus one day.
It all happened like this:

  1. I got on the bus one day and sat next to a very big guy. I could not fit properly on my side of the seat and my leg was off the side, preventing people from comfortably walking past me.
  2. At the next stop, a guy who had a double-seat to himself got off;
  3. Then, a lady who was already sitting comfortably next to a thin guy moved to take over the whole double-seat (she clearly felt that sharing a seat with someone was beneath her :));
  4. I, however, did not mind at all and moved to sit next to the thin guy;
  5. Then, much to my amusement, a guy got on the bus and sat next to the lady, putting her in no better position than she started;

This amused me, so I thought I’d re-create the scene using jQuery (below).

Click the “Dave, Show Me What Happened” button (press F11 to go full screen):

The code which is the engine for my little melodrama is:

var undef = 'undefined'; // saves us from creating and throwing away a string every time.

if(typeof(busNamespace) === undef) {
	var busNamespace = { };
}

if(typeof(constants) === undef) {
	var constants = { };
}
		
$(function() {

	var revealStoryButton = $("#reveal");

	$("#story").hide();
	
	revealStoryButton.text(constants.readMore);
	
	revealStoryButton.click(function() {	
		var thisButton = $(this);
	
		$("#story").slideToggle(400, function(){
			if(thisButton.text() === constants.readMore) {
				thisButton.text(constants.hideStory);
			}
			else{
				thisButton.text(constants.readMore)
			}
		});		
	});

	busNamespace.bus = $('#bus');
	busNamespace.dave = $('<img id="dave" src="' + constants.urlOfPersonImage + '" />');
	busNamespace.lady = $('<img id="lady" src="' + constants.urlOfPersonImage +  '" />');
	busNamespace.someGuyWhoGotOff = $('<img id="someGuyWhoGotOff" src="' + constants.urlOfPersonImage + '" />');			
	busNamespace.newGuyGetsOn = $('<img id="newGuyGetsOn" src="' + constants.urlOfPersonImage + '" />');	
	
	busNamespace.bus.prepend(busNamespace.someGuyWhoGotOff);
	busNamespace.bus.prepend(busNamespace.dave);
	busNamespace.bus.prepend(busNamespace.lady);			
	busNamespace.newGuyGetsOn.hide();
	busNamespace.bus.append(busNamespace.newGuyGetsOn);
	
	busNamespace.daveOriginalPosition = { left: busNamespace.dave.css('left'), top: busNamespace.dave.css('top') };
	busNamespace.ladyOriginalPosition = { left: busNamespace.lady.css('left'), top: busNamespace.lady.css('top') };
	busNamespace.someGuyWhoGotOffOriginalPosition = { left: busNamespace.someGuyWhoGotOff.css('left'), top: busNamespace.someGuyWhoGotOff.css('top') };
	busNamespace.newGuyGetsOnOriginalPosition = { left: busNamespace.newGuyGetsOn.css('left'), top: busNamespace.newGuyGetsOn.css('top') };
	
	$('#onWithTheShow').on('click', busNamespace.someGuyWhoGotOffGetsOff);	
	$('#reset').on('click', busNamespace.resetPlayers);
});

(function(ns) {

	var bus;
	var dave;
	var lady;
	var someGuyWhoGotOff;
	var newGuyGetsOn;
	var daveOriginalPosition;
	var ladyOriginalPosition;
	var someGuyWhoGotOffOriginalPosition;
	var newGuyGetsOnOriginalPosition;
	
	ns.someGuyWhoGotOffGetsOff = function() {
		ns.someGuyWhoGotOff.animate({left: "+=120"}, 1000)
						.animate({top: "-=125"}, 1000)
						.animate({left: "-=120", opacity: 0}, 1000, ns.ladyAndDaveMove);							
	};
	
	ns.ladyAndDaveMove = function() {
		ns.dave.animate({left: "-=90"}, 1000)
			.animate({top: "-=70"}, 1000);
		ns.lady.animate({left: "-=150"}, 1000)
			.animate({top: "+=150"}, 1000, ns.ladyAndDaveMoveAgain);
	};
	
	ns.ladyAndDaveMoveAgain = function() {
		ns.dave.animate({top: "-=300"}, 1000)
			.animate({left: "+=90"}, 1000);
		ns.lady.animate({top: "+=98"}, 1000)
			.animate({left: "-=140"}, 1000, ns.newGuyGetsOnAndSitsNextToLady);		
	};
	
	ns.newGuyGetsOnAndSitsNextToLady = function() {
		ns.newGuyGetsOn.fadeIn(1000);
		ns.newGuyGetsOn.animate({left: "+=150"}, 1000)
					.animate({top: "+=128"}, 1000)
					.animate({left: "-=92"}, 1000);
	};
	
	ns.resetPlayers = function() {
		ns.dave.fadeOut(600, function(){
			ns.dave.hide();
			ns.dave.css({ 'left' : ns.daveOriginalPosition.left, 'top': ns.daveOriginalPosition.top });
			ns.dave.fadeIn(600);
		});		
		
		ns.lady.fadeOut(600, function(){
			ns.lady.hide();
			ns.lady.css({ 'left' : ns.ladyOriginalPosition.left, 'top': ns.ladyOriginalPosition.top });
			ns.lady.fadeIn(600);	
		});
				
		ns.someGuyWhoGotOff.css({ 
				'left' : ns.someGuyWhoGotOffOriginalPosition.left, 
				'top': ns.someGuyWhoGotOffOriginalPosition.top
			}).animate({opacity: 1}, 2000);
		
		
		ns.newGuyGetsOn.css({ 
				'left' : ns.newGuyGetsOnOriginalPosition.left, 
				'top': ns.newGuyGetsOnOriginalPosition.top
			}).hide(600);
				
	};
	
})(busNamespace);

(function(ns) {
	ns.hideStory = 'Click here to hide story';
	ns.readMore = 'Click here to read more...';	
	ns.urlOfPersonImage = "https://davidrogers.id.au/wp_jsPosts/person.png";
})(constants);

Even when having fun, I try to follow best practices, such as the use of namespaces. First code for 2013!!!

Learn Javascript – the Language Itself

One of the worst pieces of advice that I have ever received was from an architect / team leader which I used to work with. He told me,
“Don’t waste your time learning Javascript. Just learn one of the modern libraries like jQuery.”

As you can probably figure out, this guy was a bad advice machine, with terrible judgment to boot.

In a Douglas Crockford video I once watched, he lamented that one of the big problems which plagued Javascript was that developers started writing it without actually learning it first. As I trust the great Douglas Crockford over the aforementioned incompetent architect, I decided to learn Javascript.

The reason I bring this up is because the other day I was reading the jQuery API documentation about the map() function (refreshing my memory – it had been a while). In the examples section, the following code was set out:

...
<script type="text/javascript">// <![CDATA[
$.fn.equalizeHeights = function() {
  var maxHeight = this.map(function(i,e) {
    return $(e).height();
  }).get();

  return this.height( Math.max.apply(this, maxHeight) );
};

$('input').click(function(){
  $('div').equalizeHeights();
});
// ]]></script>

If I was not fluent in javascript, I would not have understood the following fragment of code:

Math.max.apply(this, maxHeight)

The apply method is quite particular to javascript. Sure, I could have Googled that code and just chucked it in my project to get it working. But I wouldn’t have a clue why it works. And many developers I know aren’t even aware of the apply method, let alone what it does. Don’t be surprised by that. Really!

As it turned out, the architect I mentioned above did not understand Javascript or AJAX at all. This became apparent in various team discussions, and made for some great giggling amongst the junior developers.

So listen to Crockford. Learn Javascript! With the HTML 5 stuff and the advent of client-side, data-binding frameworks, developers will be writing more Javascript than they ever have before.