Category Archives: javascript

Fixing authors when importing a split WXR file for WordPress

If you have a large WordPress Extended RSS import file, WordPress suggests that you split the file in order to create multiple smaller files.  If you do this, then each file will have the same authors.  If you import the second file, third, fourth, etc… then those authors will be seen as duplicates and dropped (and all imported posts will be credited to ‘admin’) unless you go through each author and select them in the “or assign this post to existing author” combo boxes.   These combo boxes become available after you select a file and press “Import.”

If you are importing multiple files because a single one is too big, there is a chance you could have way too many authors to go through each one and do this.  This post describes a hack method of populating all of these combo boxes using jQuery and the browsers development tools.

I had to import 6 WXR files that i created, each one around 80Megs, and a total of almost 500 authors.

1) Get jQueryify here and add the bookmarklet per the instructions.

2) When you get to the “assign authors” page, right-click on the browser page and select “Inspect Element” (Chrome), or “Inspect with Firebug” in Firefox (install firebug if you haven’t already). In IE press F12 to start the Dev Tools.

3) Press the jQuerify bookmarklet button that you installed from step 1. This will turn on jQuery for the page.

4) Go to the console tab, enable the console if you need to, paste this code in and press RUN:
jQuery.noConflict(); //Disable jQuery $ just incase.

jQuery('#authors li').each(function() {
// Get the author login text
var username = jQuery(this).find('strong').html();
var author_login = jQuery.trim((username.split('('))[0]);

//Figure out which option this author is in the drop down.
var selectOptionval = -1;
jQuery(this).find('select option').each(function(){
if (jQuery(this).html() === author_login) {
selectOptionval = jQuery(this).val();
return false;//quit .each() early.
}
});

// Set the combo box to this author's option key.
jQuery(this).find('select').val(selectOptionval);
// For test...
//console.log(author_login + ": " + selectOptionval);
});


jQuery ready function with holdReady

I used this the other day in GWT, prior to backing it out and placing style classes in better places so i could use straight CSS targeting.  But, I thought it could be useful and it uses the new jQuery.holdReady function so here it is.

My goal was this: In order to get the correct horizontal scrollbar in a GWT app, I had to target the div right ABOVE the root div placed in my Root View (which contains the structural layout of my page). I used jQuery to target that div, but they went back and placed a styleclass in the RootLayoutPanel widget instead so that i could target this more cleanly (within GWT) with CSS’s :nth-of-type / :nth-child selector.  I found the exact DIV that i needed to target by placing overflow-x: auto !important on all the parent divs until it worked ‘correctly.’

The ‘pageScrolling’ style class is defined with only overflow-x: auto !important

/* Startup functions */

/*
* First, place a hold on the document ready function… GWT loads more stuff
* after the DOM loads, and we need to add a little extra delay to account for that.
*
* By using document ready and hold ready together, i am able to make sure that the
* startup function runs after the page is completely ready.
*/
jQuery.holdReady(true);

// Set up the on-DOM-ready function
jQuery(function($) {
// Find the DIV that should handle scrolling for the “body” and mark it
$(‘.myRootPanel’).parent().addClass(‘pageScrolling’);
});

/*
* GWT needs to create its structure, which happens after DOM loads??
* Therefore, hold the document READY function until an expected element
* is detected
*
* (ps.. This holds ALL jQuery ready functions anywhere the system… if there are any)
*/
var waitForPageToLoad = function() {
if (jQuery(‘.myRootPanel’).size() > 0) {
// it exists now.. trigger the ready function
jQuery.holdReady(false);
} else {
// Doesn’t exist yet.. wait some randomly selected time period.
setTimeout( waitForPageToLoad, 200 );
}
};

/*
* Trigger the timeout function to wait for the page to REALLY load,
* and then release the document READY hold.
*/
waitForPageToLoad();


HTML5 Canvas: scrolling background and selecting/dragging shapes

I have been wanting to do this for a while and just had a chance to mess around with it this week.  I also had more motivation after spending Saturday in an “Designing (for) Interactions” workshop by Dan Mall, and hosted by RefreshPGH.  The key concepts were HTML5, CSS3, some jQuery, and generally getting some good experience on how people in the real world design web sites.  ( I really liked the boilerplate website, font squirrel, and of course A List Apart articles! )

Anyway, the thing I have been wanting to mess around with is the canvas.  I have recently been working on some mobile application prototypes and I am getting familiar with some of the components that exist there.  I wanted to see if I could put together a canvas demo to replicate a ScrollView, as well as being able to throw stuff in and manipulate it.  So here’s my example,with a little explanation.  It is a little hacked together because I put it together pretty quickly, so don’t grade on neatness and optimization —  many of the functions and comments are still very much what Simon Sarris wrote and I pieced things in around his code.  There are also bits and pieces of example code used from multiple places, like here.

The Details…

I started out with the code that Simon Sarris wrote for selectable shapes in a canvas.  After looking and understanding what he was doing, I refactored the code to be object oriented so it was more expandable.  My Goal: Understand the code well enough that I can edit it, make it expandable, and experiment with creating the “ScrollView” effect that I see on mobile devices.

Here is the canvas tag I used:

<canvas id="canvas_clickAndDragOO" width="400" height="300" style="border: 1px black solid;">
   This text is displayed if your browser does not support HTML5 Canvas.
</canvas>

The manipulation of the shapes is exactly how Simon wrote it, so check out his page for that explanation.  The main thing I added was the scrollable content.

Key Points:

  • I have two main things in the javascript:  a Canvas class and Shapes.  The shapes are pretty basic, so the Canvas class is the most interesting.  It maintains two canvas elements: one for the content and one for the view.  The content canvas is never added to the view and is only maintained behind the scenes as a javascript variable.  Everything is written to that content canvas and the context drawImage method is used to send a section of it to the viewable canvas.
  • To make the view ‘scroll,’ an X and Y value are maintained to specify the coordinates of the top left corner of the viewable canvas on the content canvas.  When the background of the canvas is clicked and dragged, this X and Y value are updated.  The draw method simply reacts to these new values and sends an new section of the content canvas out to the viewable canvas.  The viewable canvas is simply a window to a section of the content canvas.

The “content canvas” (hidden) is automatically created twice the size of the “viewable canvas” for this demo, but this and other things are configurable if you look at the Canvas object in the code.  Just include this in a webpage via a script tag and it should work with the canvas above.  (UPDATE: I pasted this here so it could be read and copied easier)

UPDATE 2:  This code doesn’t work on < IE8 using the excanvas.js library because that library does not support the getImageData function!  I would have to find a way around that in order for it to be backwards compatible.  Also, older IE browsers do not support the pageX and pageY properties on the mouse events. This issue can be solved quickly by looking here (search the page for “pageX”).

UPDATE 3: I cannot run a demo on this wordpress site, so here is a link to a zip file demo.  Unzip it and open index.html in your browser. This is on my new site … same blog, but i’m yet to redirect everything there…

/*
 * My object oriented version of click and drag
 *
 */

function Canvas( data ) {
  data = data || {};
  var self = this;

  this.canvasID = data.canvasID || 'canvas';
  this.viewableCanvas = document.getElementById(this.canvasID);
  this.vctx = data.canvasContext || this.viewableCanvas.getContext('2d');
  // set our events. Up and down are for dragging,
  // double click is for making new boxes
  this.viewableCanvas.onmousedown = function(e){ Canvas.prototype.mouseDownAction.call( self, e ) };
  this.viewableCanvas.onmouseup = function(e){ Canvas.prototype.mouseUpAction.call( self, e ) };
  this.viewableCanvas.onmouseout = function(e){ Canvas.prototype.mouseUpAction.call( self, e ) };
  this.viewableCanvas.ondblclick = function(e){ Canvas.prototype.mouseDblClickAction.call( self, e ) };

  this.isValid = Canvas.IS_INVALID;

  this.canvasContentObjectsList = [];

  // we use a content canvas to draw individual shapes.  This is larger than the viewable canvas.  
  // only a section of this is passed to the viewable canvas
  var contentCanvasHeight = data.contentCanvasHeight || this.viewableCanvas.height * 2;
  if (contentCanvasHeight < this.viewableCanvas.height) { contentCanvasHeight = this.viewableCanvas.height }
  var contentCanvasWidth = data.contentCanvasWidth || this.viewableCanvas.width * 2;
  if (contentCanvasWidth < this.viewableCanvas.width) { contentCanvasWidth = this.viewableCanvas.width }
  this.contentCanvas = document.createElement('canvas');
  this.contentCanvas.height = contentCanvasHeight;
  this.contentCanvas.width = contentCanvasWidth;
  this.cctx = this.contentCanvas.getContext('2d'); // content context
  // These X and Y values are the top left corner of the "viewable window" of this content canvas
  this.ccViewX = Math.max( (contentCanvasWidth/2) - (this.viewableCanvas.width/2), 0 );
  this.ccViewY = Math.max( (contentCanvasHeight/2) - (this.viewableCanvas.height/2), 0 );

  // Max values for the ccView X and Y vals
  this.ccViewXMax = this.cctx.canvas.width - this.vctx.canvas.width;
  this.ccViewYMax = this.cctx.canvas.height - this.vctx.canvas.height;

  this.isContentObjectDragAction = false;
  this.isContentCanvasDragAction = false;

  // the X and Y coordinate for when the mousedown event is triggered
  this.contentCanvasDragStartX = 0;
  this.contentCanvasDragStartY = 0;

    //fixes a problem where double clicking causes text to get selected on the canvas
  this.viewableCanvas.onselectstart = function () { return false; }

  // fixes mouse co-ordinate problems when there's a border or padding
  // see getMouse for more detail
  this.stylePaddingLeft = 0;
  this.stylePaddingTop = 0;
  this.styleBorderLeft = 0;
  this.styleBorderTop = 0;
  if (document.defaultView && document.defaultView.getComputedStyle) {
    this.stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(this.viewableCanvas, null)['paddingLeft'], 10)      || 0;
    this.stylePaddingTop  = parseInt(document.defaultView.getComputedStyle(this.viewableCanvas, null)['paddingTop'], 10)       || 0;
    this.styleBorderLeft  = parseInt(document.defaultView.getComputedStyle(this.viewableCanvas, null)['borderLeftWidth'], 10)  || 0;
    this.styleBorderTop   = parseInt(document.defaultView.getComputedStyle(this.viewableCanvas, null)['borderTopWidth'], 10)   || 0;
  }

// The selection color and width. Right now we have a red selection with a small width
  this.selectedColor = data.selectedColor || '#CC0000';
  this.selectedWidth = data.selectedwidth || 2;

  // since we can drag from anywhere in a node
  // instead of just its x/y corner, we need to save
  // the offset of the mouse when we start dragging.
  this.offsetx = this.ccViewX;
  this.offsety = this.ccViewY;

  this.currentlySelectedContentObject = null;

    // make draw() fire every INTERVAL milliseconds
  setInterval(function() { Canvas.prototype.draw.call( self ) }, 1000/(data.interval?data.interval:40) );
}
// static members
Canvas.IS_VALID = true;
Canvas.IS_INVALID = false;

// create a random number between two ints, inclusive
Canvas.randomFromTo = function( from, to ) {
  return Math.floor(Math.random() * (to - from + 1) + from);
}

//wipes the canvas context
Canvas.prototype.clear = function( ctx ) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}

// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
Canvas.prototype.draw = function( ) {
  if ( !this.isValid && this.cctx ) {
    //Canvas.prototype.clear.call( this, ctx );
    this.clear( this.cctx );

    // Add stuff you want drawn in the background all the time here
    //Draw a grid to assist in seeing the background scroll
    this.drawGrid();

    // draw all boxes
    var l = this.canvasContentObjectsList.length;
    for (var i = 0; i < l; i++) {
      this.canvasContentObjectsList[i].drawshape({
        ctx: this.cctx
      });
    }

    // draw selection
    // right now this is just a stroke along the edge of the selected box
    var selectedObj = this.currentlySelectedContentObject;
    if (selectedObj != null) {
      selectedObj.highlightBorder({
        ctx: this.cctx,
        selectedColor: this.selectedColor,
        selectedWidth: this.selectedWidth
      });
    }

    // copy section of content canvas out to the viewable canvas
    try {
      this.clear( this.vctx );
      this.vctx.drawImage(
        this.cctx.canvas,
        this.ccViewX,
        this.ccViewY,
        this.viewableCanvas.width,
        this.viewableCanvas.height,
        0,
        0,
        this.viewableCanvas.width,
        this.viewableCanvas.height
      )
    } catch(e) {
      // Do nothing.  a draw error may occur because we are updating this canvas
      // before the cctx.canvas is done drawing.
    }

    // Add stuff you want drawn on top all the time here

    this.isValid = Canvas.IS_VALID;
  }
}

// draw grid lines, but only on the part visible
Canvas.prototype.drawGrid = function( data ) {

  // Draw gridlines to help with scrolling
  for (var x = (this.ccViewX + 0.5); x < (this.ccViewX + this.vctx.canvas.width); x += 40) {
    this.cctx.moveTo(x, 0);
    this.cctx.lineTo(x, this.ccViewY + this.vctx.canvas.height);
  }
  for (var y = (this.ccViewY + 0.5); y < (this.ccViewY + this.vctx.canvas.height); y += 40) {
    this.cctx.moveTo(0, y);
    this.cctx.lineTo(this.ccViewX + this.vctx.canvas.width, y);
  }
  this.cctx.strokeStyle = "#eee";
  this.cctx.stroke();
}

// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
Canvas.prototype.getMouseCoordinates = function(e) {
      var element = this.viewableCanvas, offsetX = 0, offsetY = 0;

      if (element.offsetParent) {
        do {
          offsetX += element.offsetLeft;
          offsetY += element.offsetTop;
        } while ((element = element.offsetParent));
      }

      // Add padding and border style widths to offset
      offsetX += this.stylePaddingLeft;
      offsetY += this.stylePaddingTop;

      offsetX += this.styleBorderLeft;
      offsetY += this.styleBorderTop;

      var mx = e.pageX - offsetX + this.ccViewX;
      var my = e.pageY - offsetY + this.ccViewY;

      // These are the mouse coordinates on the VIEWABLE CANVAS
      return {
        mouseX: mx,
        mouseY: my
      };
}

// Happens when the mouse is clicked in the canvas
Canvas.prototype.mouseDownAction = function(e){

  var self = this;
  var mouseMoveFunction = function(e){ Canvas.prototype.mouseMoveAction.call( self, e ) };

  var mouseCoords = this.getMouseCoordinates(e);
  //Canvas.prototype.clear.call( this, ctx );
  this.clear( this.cctx );
  var l = this.canvasContentObjectsList.length;
  for (var i = l-1; i >= 0; i--) {
    // draw shape onto ghost context
      this.canvasContentObjectsList[i].drawshape({
        ctx: this.cctx,
        fill: 'black'
      });

    // get image data at the mouse x,y pixel
    var imageData = this.cctx.getImageData(mouseCoords.mouseX, mouseCoords.mouseY, 1, 1);
    //var index = (mouseCoords.mouseX + mouseCoords.mouseY * imageData.width) * 4;

    // if the mouse pixel exists, select and break
    if (imageData.data[3] > 0) {
      this.currentlySelectedContentObject = this.canvasContentObjectsList[i];
      this.offsetx = mouseCoords.mouseX - this.currentlySelectedContentObject.x;
      this.offsety = mouseCoords.mouseY - this.currentlySelectedContentObject.y;
      this.currentlySelectedContentObject.x = mouseCoords.mouseX - this.offsetx;
      this.currentlySelectedContentObject.y = mouseCoords.mouseY - this.offsety;
      this.isContentObjectDragAction = true;
      this.viewableCanvas.onmousemove = mouseMoveFunction;
      this.isValid = Canvas.IS_INVALID;
      //Canvas.prototype.clear.call( this, ctx );
      this.clear( this.cctx );
      return;
    }
  }
  // Register a drag action for the whole canvas
  if ( !this.isContentCanvasDragAction ) {
    this.contentCanvasDragStartX = mouseCoords.mouseX;
    this.contentCanvasDragStartY = mouseCoords.mouseY;
    this.originalCCViewX = this.ccViewX;
    this.originalCCViewY = this.ccViewY;
  }
  this.isContentCanvasDragAction = true;
  this.viewableCanvas.onmousemove = mouseMoveFunction;
  // havent returned means we have selected nothing
  this.currentlySelectedContentObject = null;
  // clear the ghost canvas for next time
  //Canvas.prototype.clear.call( this, ctx );
  this.clear( this.cctx );
  // invalidate because we might need the selection border to disappear
  this.isValid = Canvas.IS_INVALID;
}

// Happens when the mouse is moving inside the canvas
Canvas.prototype.mouseMoveAction = function(e){
  var mouseCoords = null;

  if ( this.isContentObjectDragAction ){
    mouseCoords = this.getMouseCoordinates(e);

    this.currentlySelectedContentObject.x = mouseCoords.mouseX - this.offsetx;
    this.currentlySelectedContentObject.y = mouseCoords.mouseY - this.offsety;   

    // something is changing position so we better invalidate the canvas!
    this.isValid = Canvas.IS_INVALID;
  }

  if ( this.isContentCanvasDragAction ) {
    mouseCoords = this.getMouseCoordinates(e);

    xChange = (mouseCoords.mouseX - this.contentCanvasDragStartX);
    yChange = (mouseCoords.mouseY - this.contentCanvasDragStartY);

    // Must move 30 pixels before scrolling.
    if (Math.abs(xChange) > 30 || Math.abs(yChange) > 30) {
      var newX = this.originalCCViewX - xChange;
      newX = newX < 0 ? 0 : newX;
      newX = newX > this.ccViewXMax ? this.ccViewXMax : newX;
      this.ccViewX = newX;

      var newY = this.originalCCViewY - yChange;
      newY = newY < 0 ? 0 : newY;
      newY = newY > this.ccViewYMax ? this.ccViewYMax : newY;
      this.ccViewY = newY;

      this.isValid = Canvas.IS_INVALID;
    }
  }
}

Canvas.prototype.mouseUpAction = function(){
  this.isContentObjectDragAction = false;
  this.isContentCanvasDragAction = false;
  this.viewableCanvas.onmousemove = null;
}

Canvas.prototype.addContentObject = function( obj ) {
  // Verify that this is the correct object type so we have X and Y coordinates

  this.canvasContentObjectsList.push( obj );
  this.isValid = Canvas.IS_INVALID;
}

// adds a new node
Canvas.prototype.mouseDblClickAction = function(e) {
  var mouseCoords = this.getMouseCoordinates(e);
  var randomColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);

  switch( Canvas.randomFromTo( 1, 3 ) ) {
    case 1:
      this.addContentObject( new Circle({
          x: mouseCoords.mouseX,
          y: mouseCoords.mouseY,
          r: Canvas.randomFromTo( 20, 50 ),
          fill: randomColor
        })
      );
      break;
    case 2:
      this.addContentObject( new Rectangle({
          x: mouseCoords.mouseX,
          y: mouseCoords.mouseY,
          w: Canvas.randomFromTo( 20, 100 ),
          h: Canvas.randomFromTo( 20, 100 ),
          fill: randomColor
        })
      );
      break;
    case 3:
    default:
      this.addContentObject( new Triangle({
          x: mouseCoords.mouseX,
          y: mouseCoords.mouseY,
          w: Canvas.randomFromTo( 20, 100 ),
          h: Canvas.randomFromTo( 20, 100 ),
          fill: randomColor
        })
      );
      break;
  }
}

/***********************************************************
 * Shape super class
 *
 ***********************************************************/

function Shape( data ) {
  data = data || {};

  this.shape = data.shape || 'Shape';
  this.shapeID = data.id || 'Shape';

  this.fill = data.fill || '#444444';

  this.x = data.x;
  this.y = data.y;
}

// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
Shape.prototype.drawshape = function( data ) {
    if (!data) return;

  // fill can be overridded if passed in
  data.ctx.fillStyle = data.fill || this.fill;

  // subclass specific
}

Shape.prototype.highlightBorder = function( data ) {
  // Handle the basic, standard highlight things here.
  if (!data) return;

  data.ctx.strokeStyle = data.selectedColor;
  data.ctx.lineWidth = data.selectedWidth;
}

/***********************************************************
 * Rectangle super class
 *
 ***********************************************************/

// Create subclass and reassign constructor to self.
Rectangle.prototype = new Shape();
Rectangle.prototype.constructor = Rectangle;
function Rectangle( data ) {
  data = data || {};

  Shape.call( this, data );

  this.w = data.w;
  this.h = data.h;
}

// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
Rectangle.prototype.drawshape = function( data ) {
  Shape.prototype.drawshape.call(this, data); 

  // We can skip the drawing of elements that have moved off the screen:
  if (this.x > data.ctx.canvas.width || this.y > data.ctx.canvas.height) return;
  if (this.x + this.w < 0 || this.y + this.h < 0) return;

  data.ctx.fillRect(this.x,this.y,this.w,this.h);
}

Rectangle.prototype.highlightBorder = function( data ) {
  Shape.prototype.highlightBorder.call(this, data);
  data.ctx.strokeRect(this.x,this.y,this.w,this.h);
}

/***********************************************************
 * Circle super class
 *
 ***********************************************************/

// Create subclass and reassign constructor to self.
Circle.prototype = new Shape();
Circle.prototype.constructor = Circle;
function Circle( data ) {
  data = data || {};

  Shape.call( this, data );

  this.r = data.r; //radius
}

// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
Circle.prototype.drawshape = function( data ) {
  Shape.prototype.drawshape.call(this, data); 

  // We can skip the drawing of elements that have moved off the screen:
  if (this.x > data.ctx.canvas.width || this.y > data.ctx.canvas.height) return;
  if (this.x + this.r < 0 || this.y + this.r < 0) return;

  this.drawCircle( data );
}

Circle.prototype.highlightBorder = function( data ) {
  Shape.prototype.highlightBorder.call(this, data); 

  data['borderOnly'] = true;
  this.drawCircle( data );
}

Circle.prototype.drawCircle = function( data ) {
    // Draw a circle using the arc function.
  data.ctx.beginPath();

  // Arguments: x, y, radius, start angle, end angle, anticlockwise
  data.ctx.arc(this.x, this.y, this.r, 0, 360, false);
  if ( !data['borderOnly'] ) {
    data.ctx.fill();
  } else {
    data.ctx.stroke();
  }

  data.ctx.closePath();
}

/***********************************************************
 * Triangle super class
 *
 ***********************************************************/

// Create subclass and reassign constructor to self.
Triangle.prototype = new Shape();
Triangle.prototype.constructor = Triangle;
function Triangle( data ) {
  data = data || {};

  Shape.call( this, data );

  this.w = data.w;
  this.h = data.h;
}

// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
Triangle.prototype.drawshape = function( data ) {
  Shape.prototype.drawshape.call(this, data); 

  // We can skip the drawing of elements that have moved off the screen:
  if (this.x > data.ctx.canvas.width || this.y > data.ctx.canvas.height) return;
  if (this.x + this.w < 0 || this.y + this.h < 0) return;

  this.drawTriangle( data );
}

Triangle.prototype.highlightBorder = function( data ) {
  Shape.prototype.highlightBorder.call(this, data);
  data['borderOnly'] = true;
  this.drawTriangle( data );
}

Triangle.prototype.drawTriangle = function( data ) {
  data.ctx.beginPath();
  // Start from the top-left point.
  data.ctx.moveTo( this.x, this.y - (this.h / 2) ); // give the (x,y) coordinates
  data.ctx.lineTo( this.x + (this.w / 2), this.y + (this.h / 2) );
  data.ctx.lineTo( this.x - (this.w / 2), this.y + (this.h / 2) );
  data.ctx.lineTo( this.x, this.y - (this.h / 2) );

  // Done! Now fill the shape, and draw the stroke.
  // Note: your shape will not be visible until you call any of the two methods.
  if ( !data['borderOnly'] ) {
    data.ctx.fill();
  } else {
    data.ctx.stroke();
  }
  data.ctx.closePath();
}

// start it up!
window.onload = function(){
  var OO_canvas = new Canvas( { canvasID: 'canvas_clickAndDragOO' });

  var r1 = new Rectangle({
    x: 800,
    y: 700,
    w: 40,
    h: 70,
    fill: '#FFC02B'
  });
  OO_canvas.addContentObject( r1 );

  var r2 = new Rectangle({
    x: 780,
    y: 780,
    w: 70,
    h: 40,
    fill: '#2BB8FF'
  });
  OO_canvas.addContentObject( r2 );

  var t1 = new Triangle({
    x: 700,
    y: 600,
    w: 70,
    h: 40,
    fill: '#900'
  });
  OO_canvas.addContentObject( t1 );

  var t2 = new Triangle({
    x: 900,
    y: 600,
    w: 70,
    h: 40,
    fill: '#009'
  });
  OO_canvas.addContentObject( t2 );

  var c1 = new Circle({
    x: 675,
    y: 730,
    r: 30,
    fill: '#459'
  });
  OO_canvas.addContentObject( c1 );

  var c2 = new Circle({
    x: 950,
    y: 730,
    r: 30,
    fill: '#954'
  });
  OO_canvas.addContentObject( c2 );
};

Browser cache refreshing for CSS and Javascript

After reading an article on it, and having an issue at work concerning it, I put together a script that should keep CSS and JS files refreshed at a configured interval.  I’d like to get comments on it, or just make it available if needed:

Here’s the script, I stored it in a separate JS file and called it into my webpage ( a jsp page) with a script element:

/**
* killCache.js
*
* Small js script that allows for appending a "code" to URLs of CSS or JS files (or
* anything that uses a LINK element) in order to force the browser to update from the
* files and not from cache.
* /

/**
* Round a value to a given interval.
*
* For example:
* (1) if the value is 4 and the interval is 10, then the result will be 0.
* (2) if the value is 755.3 and the interval is 100, the result will be 800.
*
* @param value the current value to check and round
* @param interval the interval to round the value to
* @return the rounded value
*/
function roundTo(value, interval) {
// round the value to the nearest interval
value /= interval;
value = Math.round(value);
return value * interval;
}

/**
* Create a string 'code' that changes every INTERVAL.  this code can be appended to CSS or JS files
* that change in order to make sure the user is always getting the correct version and not a cached version.
*
* @return a string code that changes every INTERVAL so that some files are not cached.
*/
function getRefreshCode() {
var currentTime = new Date();
var intervalToUse = 30; // round minutes to nearest 30 minute interval
var updateInterval = roundTo(currentTime.getMinutes(), intervalToUse);

return updateInterval + "_" + currentTime.getHours() + "_" + currentTime.getDay();
}

/**
* Create a link element in the header where the href also contains a dynamic
* string that forces a periodic update of cache
*/
function addUpdatingLinkToHead(rel, type, href, title) {
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');

if (rel != null) { link.rel = rel; }
link.type = type;
link.href = href + "?" + getRefreshCode(); // refresh code is added here.
if (title != null) { link.title = title; }

head.appendChild(link);
}

/**
* Create a script element in the header where the href also contains a dynamic
* string that forces a periodic update of cache
*/
function addUpdatingScriptToHead(type, src) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');

script.type = type;
script.src = src + "?" + getRefreshCode(); // refresh code is added here.

head.appendChild(script);
}

and then I the script itself, and my CSS file to the web page like this:

<!-- Small js file that creates CSS links & js scripts and forces them to update occasionally -->
<script src="/js/killCache.js" type="text/javascript"></script>
<script type="text/javascript">
  addUpdatingLinkToHead("stylesheet", "text/css", "/theme/cssFile1.css", "Style");
  addUpdatingLinkToHead("stylesheet", "text/css", "/theme/cssFile2.css", "Style");
</script>

jQuery

I picked up the book jQuery In Action, and I think I love this library!  Actually, I picked it up a while ago and I’m just getting around to writing about it.  I recently built a website for watching the building of the new arena in Pittsburgh (which is down now because the building is big enough to block the webcam), and I used jQuery to do some really nice fades for image thumbnails.  The only problem was, I was following pre-written instructions and did not know what was really going on.  It did, however, introduce me to the magic of jQuery!

CSS consistency across browsers

One of the best things about jQuery is that you can (almost always) just put a line of jQuery in my web page and it will work across all of the major browsers.  I have had some very small issues with this, but nothing major compared to the amount of code it takes in straight javascript to do the same thing.

There’s also the selectors.  If you know CSS, then you can use jQuery.  jQuery works by creating a wrapped set of elements and performing some tasks on each element in the wrapped set.  The book i read uses the term wrapped set, but if that term is confusing you can just think of it as a group of elements that matches the selector you chose to use.

Any CSS selector can be used to query the page and create this wrapped set.  It even allows for CSS3 selectors and a couple of jQuery-specific extended selectors.  For example, consider the child selector #formId > h1 that would select all h1 elements nested inside the element with the id #formId.  This is not supported in IE6, but with jQuery you can use it anyway:

$('#formId > h1').css('color','red');

Simplify, Simplify, Simplify

jQuery is just simple.  You only need to know the CSS selectors to get the maximum power from jQuery, not which browser supports which selectors, etc.  The javascript file is much neater and more readable.  Take this example of code that was used to put focus on the first input on a webpage:

function setFocusOnFirstLocationOfInputType(type){
var elementsInputs = document.getElementsByTagName("input");
for (i=0; i < elementsInputs.length; i++) {
if(elementsInputs[i].type == type){
elementsInputs[i].focus();
break;
}
}
}

and now consider the jQuery that does the same:

function setFocusOnFirstLocationOfInputType(type){
$("input[type="+type+"]:first").focus();
}

Extendable!

The jQuery core library does everything I’ve mentioned and a lot more, and it is small.  If you need more, there are a lot of plugins available that supply very complex features (made simple!) like accordian or sliding menus (jQueryUI plugin), or extended selectors with regular expressions.

Some links to additional info on the library:


Firebug Lite!

I found a new tool yesterday that I hope solves a lot of my issues!  Firebug Lite! If you are a developer, you are probably familiar with firebug for firefox already.  It is a great tool for debugging just about all aspects of a web page.

I work a lot with JSF pages created using Java Pure Faces, so I do a lot of my CSS tweaking after the fact.  We create web applications that will be used by just about anyone, and unfortunately, people still use IE6 (or IE at all).  Firebug Lite allows you to add a debugging tool very similar to the Firefox Firebug tool into any browser by simply adding a <script> tag like:

<script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>

This is great!  I just had an issue last week where I needed this, but I only discovered yesterday in a tweet on twitter (follow my tweets – @mpickell).  The tweet directed me to tutorial9 where there is a nice write-up.

I’m really looking forward to using this tool, but I have found some issues with already.  I haven’t seen these documented, and I am not sure if they are problems with my configuration, so I am interested if anyone else sees the same thing.

  • I wasn’t able to get firebug lite working in IE6, and the CSS window on the right hand side didn’t work for IE7.  These are probably my fault, because I tested them through IETester, and not real installs.
  • I also tested in a full, normal install of Opera and had some issues.
  • I still need to test Chrome and Safari, but i’ll post that when i do.

So this is a great tool to have available, and I’m sure upcoming revisions will fix the issues I saw.  It is going to make life a lot easier.