Friday, December 30, 2011

Adding the MovingBoxes plugin to Blogger

These instructions will allow you to add a MovingBoxes widget to any Blogger post. These instruction really apply to any plugin out there, but instead of making a generic post, I think a specific example is best.

  • picture

    Olivia News Heading

    A very short exerpt goes here... more
  • picture

    Alice News Heading

    A very short exerpt goes here... more and a whole lot more text goes here, so we can see the height adjust.
  • picture

    Yin News Heading

    A very short exerpt goes here... more
  • picture

    Gerri News Heading

    A very short exerpt goes here... more
  • picture

    Tabitha News Heading

    A very short exerpt goes here... more
  • picture

    Mary News Heading

    A very short exerpt goes here... more
  • picture

    Kitty News Heading

    A very short exerpt goes here... more

Note: In the next version of MovingBoxes, I plan to change the width and panelWidth options. At that time, I will update this post.

Code, CSS and image setup

Required Files
I don't know how github would feel about directly linking to their servers. Most businesses frown upon hot-linking and will most likely block your URL, or the entire domain if the hot-linking gets too rampant. So I'd recommend copying each file below and saving it to your own server.
Note that inside of the css, you'll need to change the arrows.png url to point to the new location of the image file.

/*** Left & Right Navigation Arrows ***/
a.mb-scrollButtons {
  background: transparent url(../images/arrows.png) no-repeat;
}

If you don't have a server, you can use dropbox or your iCloud (I don't own a Mac, so I'm only guessing that it'll allow you to save javascript & css files). If you don't have a server to save the files, then first, save the image to an image hosting site, like photobucket, Flickr or something. Then you can save the javascript and css into your own blog design. The main problem with saving into your page design is that it will increase your page size and possibly make the page loading take longer. Either way you'll need to add a gadget, or add to an already existing one.

Add a Gadget
So once you have your files saved and the css file updated:
  • Go to your Dashboard > Design > Add a Gadget > Pick the "HTML/Javascript" gadget.
  • Once the popup window opens, add the following plugin css and javascript in the content window, the URL should point to your hosted files
  • Note: After testing this, it seems that adding a <link> tag inside of the HTML/Javascript gadget gets removed. I'm not sure why, but in this case we can just paste in the entire css.

    <style>
    /*** Overall MovingBoxes Slider ***/
    .mb-wrapper {
     width: 900px; /* default, this is overridden by script settings */
     min-height: 200px;
     border: 5px solid #ccc;
     margin: 0 auto;
     position: relative;
     left: 0;
     top: 0;
     border-radius: 1em;
     -moz-border-radius: 1em;
     -webkit-border-radius: 1em;
     box-shadow: inset 0 0 10px #888;
     -moz-box-shadow: inset 0 0 10px #888;
     -webkit-box-shadow: inset 0 0 10px #888;
    }
    
    /* Panel Wrapper */
    .mb-slider, .mb-scroll {
     width: 100%;
     height: 100%;
     overflow: hidden;
     margin: 0 auto;
     padding: 0;
     position: relative;
     left: 0;
     top: 0;
    
     /***(>'-')> Control Panel Font size here <('-'<)***/
     font-size: 18px;
    }
    
    /* active slider border highlight */
    .mb-active-slider {
     border-color: #999bff;
    }
    
    /*** Slider panel ***/
    .mb-slider .mb-panel {
     width: 350px;  /* default, this is overridden by script settings */
     margin: 5px 0;
     padding: 5px;
     display: block;
     cursor: pointer;
     float: left;
     list-style: none;
    }
    
    /* Cursor to arrow over current panel, pointer for all others,
    change .current class name using plugin option, currentPanel : 'current' */
    .mb-slider .mb-panel.current {
     cursor: auto;
    }
    
    /*** Inside the panel ***/
    .mb-inside {
     padding: 10px;
     border: 1px solid #999;
    }
    
    .mb-inside * {
     max-width: 100%;
    }
    
    /*** Left & Right Navigation Arrows ***/
    a.mb-scrollButtons {
     display: block;
     width: 45px;
     height: 58px;
     background: transparent url(http://css-tricks.github.com/MovingBoxes/images/arrows.png) no-repeat;
     position: absolute;
     top: 50%;
     margin-top: -29px; /* if you change the arrow images, you may have to adjust this (1/2 height of arrow image) */
     cursor: pointer;
     text-decoration: none;
     outline: 0;
     border: 0;
    }
    a.mb-scrollButtons.mb-left {
     background-position: left top;
     left: -45px;
    }
    a.mb-scrollButtons.mb-right {
     background-position: right top;
     right: -45px;
    }
    a.mb-scrollButtons.mb-left:hover {
     background-position: left bottom;
    }
    a.mb-scrollButtons.mb-right:hover {
     background-position: right bottom;
    }
    a.mb-scrollButtons.disabled {
     display: none;
    }
    
    /*** Controls added below the panels ***/
    .mb-controls {
     margin: 0 auto;
     text-align: center;
     background: #ccc;
     position: relative;
     z-index: 100;
    }
    .mb-controls a {
     color: #444;
     font: 12px Georgia, Serif;
     display: inline-block;
     text-decoration: none;
     padding: 2px;
     height: 18px;
     margin: 0 5px 0 0;
     text-align: center;
     outline: 0;
    }
    .mb-controls a.current, .mb-controls a:hover {
     color: #fff;
    }
    .mb-active-slider .mb-controls {
     background: #999bff;
    }
    </style>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
    <script src="http://css-tricks.github.com/MovingBoxes/js/jquery.movingboxes.min.js"></script>
    <script>
    jQuery(function(){
    
      jQuery('.movingboxes').movingBoxes({
        startPanel   : 1,      // start with this panel
        width        : 300,    // overall width of movingBoxes
        panelWidth   : 0.5,    // current panel width adjusted to 50% of overall width
        wrap         : false,  // if true, the panel will "wrap" (it really rewinds/fast forwards) at the ends
        buildNav     : true,   // if true, navigation links will be added
        navFormatter : function(){ return "&#9679;"; } // function which returns the navigation text for each panel
      });
    
    });
    </script>

  • Modify the "width" and "panelWidth" options (in red) as desired. Or add and/or remove options following these instructions.
  • If you don't have your own server, then you can open the movingboxes.css file in a text editor and save it into the content window; again make sure the arrow.png url is pointing to the new location in the css below.

    <style>
      /* add movingboxes.css contents here */
    </style>
    <script>
      /* add jquery.movingboxes.min.js contents here */
    </script>
Adding a blog post

Now the first step here would be to open a new post.

Switch the editor to "Edit HTML" - tab in the upper right corner, then paste in the basic plugin HTML Note, keep the <img> and <li> on the same line or blogger will add a <br> in between.

<ul class="movingboxes">
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/1.jpg" alt="picture">
    <h2>Olivia News Heading</h2>
    <p>A very short exerpt goes here... <a href="http://flickr.com/photos/justbcuz/112479862/">more</a></p>
  </li>
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/2.jpg" alt="picture">
    <h2>Alice News Heading</h2>
    <p>A very short exerpt goes here... <a href="http://flickr.com/photos/joshuacraig/2698975899/">more</a> and a whole lot more text goes here, so we can see the height adjust.</p>
  </li>
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/3.jpg" alt="picture">
    <h2>Yin News Heading</h2>
    <p>A very short exerpt goes here... <a href="http://flickr.com/photos/ruudvanleeuwen/468309897/">more</a></p>
  </li>
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/4.jpg" alt="picture">
    <h2>Gerri News Heading</h2>
    <p>A very short exerpt goes here... <a href="http://flickr.com/photos/emikohime/294092478/">more</a></p>
  </li>
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/5.jpg" alt="picture">
    <h2>Tabitha News Heading</h2>
    <p>A very short exerpt goes here... <a href="http://www.flickr.com/photos/fensterbme/499006584/">more</a></p>
  </li>
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/6.jpg" alt="picture">
    <h2>Mary News Heading</h2>
    <p>A very short exerpt goes here... <a href="#">more</a></p>
  </li>
  <li><img src="http://css-tricks.github.com/MovingBoxes/demo/7.jpg" alt="picture">
    <h2>Kitty News Heading</h2>
    <p>A very short exerpt goes here... <a href="#">more</a></p>
  </li>
</ul>

Note that the UL has a class named "movingboxes", it originally had an ID, but to make this plugin work on any future blog entries we need to make sure it's a class name we can remember.

Now, just publish your post and reload your page. The MovingBoxes plugin should initialize and look like it does at the top of this post :)

Saturday, December 17, 2011

All Images Loaded (imagesLoaded)

*NOTE* this code causes infinite loops in IE with image load errors, so I improved and turned this into a real plugin and hosted it on github.


A little jQuery function that checks if all images are loaded. If they all are, the callback function is called. Check out the latest version in this gist.

Script below:
/*
 Check if all images are loaded
 - Callback occurs when all images are loaded
 - image load errors are ignored (complete will be true)
 - Use:
   $('.wrap img').imagesLoaded(function(){
     alert('all images loaded');
   });
*/

jQuery.fn.extend({
  imagesLoaded: function( callback ) {
    var i, c = true, t = this, l = t.length;
    for ( i = 0; i < l; i++ ) {
      if (this[i].tagName === "IMG") {
        c = (c && this[i].complete && this[i].height !== 0);
      }
    }
    if (c) {
      if (typeof callback === "function") { callback(); }
    } else {
      setTimeout(function(){
        jQuery(t).imagesLoaded( callback );
      }, 200);
    }
  }
});

Use it as follows:
$(function(){
  $('.wrap img').imagesLoaded(function(){
    alert('all images loaded');
  });
});

Tuesday, December 6, 2011

jQuery Pathslider

I just created this plugin that is a very basic UI Slider (similar to the jQuery slider), but it doesn't just allow you to move the handle horizontally and vertically, it will follow any shaped curve! If you've ever used Adobe Illustrator or CorelDraw then you probably recognize the Bezier curve below.

The handle, or grip as I call it, can be dragged along the black curve. For now, it only returns values from 0 (green dot) to 100 (red dot).


The plugin is in the early stages of development and still needs a lot of work, but it is usable now.

Check out the demo page, and the builder page. If you would like to help me out, submit an enhancement or issue, or fork a copy of the plugin on Github and send me a pull request! I'd love the input!

Monday, November 14, 2011

jQuery UI Side Scroller with buttons

Once again, I got up at 2:30am wide awake... that's what happens when I fall asleep at 9:30 LOL. Anyway, I was bored so I searched for something to do. I found a question on CSS Tricks asking how to add buttons to the jQuery UI Slider. So starting with the side scroll demo as a base, I put together this demo. That is all :P

Monday, October 31, 2011

Get All indexOf From An Array - Array.allIndexOf()

This array function is an extension of Array.indexOf(), if it exists, and will return all indexes of the search element. It's named allIndexOf(). This code extends javascript and does not require jQuery. It is designed to work in older versions of IE as well, albeit a tiny bit slower.

Originally if you wanted the second instance of a search element, you'd have to call "indexOf" twice. The second time with the starting index of the first result (plus one), or once with a guessed starting index. This should simplify the process for getting any or all of the indexes.

/*
Array.allIndexOf(searchElement)
  Array [Array] - the array to search within for the searchElement
  searchElement [String] - the desired element with which to find starting indexes
*/
(function(){
  Array.prototype.allIndexOf = function(searchElement) {
    if (this === null) { return [-1]; }
    var len = this.length,
    hasIndexOf = Array.prototype.indexOf, // you know, because of IE
    i = (hasIndexOf) ? this.indexOf(searchElement) : 0,
    n,
    indx = 0,
    result = [];
    if (len === 0 || i === -1) { return [-1]; }
    if (hasIndexOf) {
      // Array.indexOf does exist
      for (n = 0; n <= len; n++) {
        i = this.indexOf(searchElement, indx);
        if (i !== -1) {
          indx = i + 1;
          result.push(i);
        } else {
          return result;
        }
      }
      return result;
    } else {
    // Array.indexOf doesn't exist
      for (n = 0; n <= len; n++) {
        if (this[n] === searchElement) {
          result.push(n);
        }
      }
      return (result.length > 0) ? result : [-1];
    }
  };
})();
Use it as follows:
var s = ["red","green","blue","red","yellow","blue","green","purple","red"];
s.allIndexOf("r"); // result [ -1 ]
s.allIndexOf("red"); // result [ 0,3,8 ]
s.allIndexOf("blue"); // result [ 2,5 ]
Try out your own strings in the demo below or full screen.

Get All indexOf From A Search String - String.allIndexOf()

This string function is an extension of String.indexOf() and will return all indexes of a search string. It's named allIndexOf. This code extends javascript and does not require jQuery.

Originally if you wanted the second instance of a search string, you'd have to call "indexOf" twice. The second time with the starting index of the first result (plus one), or once with a guessed starting index. This should simplify the process for getting any or all of the indexes. And yes, I woke up with a strange urge to write this function LOL.

/*
String.allIndexOf(searchstring, ignoreCase)
  String [String] - the string to search within for the searchstring
  searchstring [String] - the desired string with which to find starting indexes
  ignoreCase [Boolean] - set to true to make both the string and searchstring case insensitive
*/
(function(){
  String.prototype.allIndexOf = function(string, ignoreCase) {
    if (this === null) { return [-1]; }
    var t = (ignoreCase) ? this.toLowerCase() : this,
    s = (ignoreCase) ? string.toString().toLowerCase() : string.toString(),
    i = this.indexOf(s),
    len = this.length,
    n,
    indx = 0,
    result = [];
    if (len === 0 || i === -1) { return [i]; } // "".indexOf("") is 0
    for (n = 0; n <= len; n++) {
      i = t.indexOf(s, indx);
      if (i !== -1) {
        indx = i + 1;
        result.push(i);
      } else {
        return result;
      }
    }
    return result;
  }
})();
Use it as follows:
var s = "The rain in Spain stays mainly in the plain";
s.allIndexOf("ain"); // result [ 5,14,25,40 ]
s.allIndexOf("the"); // result [ 34 ]
s.allIndexOf("THE", true); // result [ 0,34 ]
Try out your own strings in the demo below or full screen.

Thursday, October 20, 2011

jQuery Rename Attribute (renameAttr)

I woke up this morning with a very unusual urge to make a rename attribute function for jQuery. I know, right.. that's just too weird! So after messing around with it, I decided that it needed to update the data as well. Here is the code I ended up with:

jQuery.fn.extend({
  renameAttr: function( name, newName, removeData ) {
    var val;
    return this.each(function() {
      val = jQuery.attr( this, name );
      jQuery.attr( this, newName, val );
      jQuery.removeAttr( this, name );
      // remove original data
      if (removeData !== false){
        jQuery.removeData( this, name.replace('data-','') );
      }
    });
  }
});

Use it as follows:
// $(selector).renameAttr(original-attr, new-attr, removeData);

// removeData flag is true by default
$('#test').renameAttr('data-test', 'data-new' );

// removeData flag set to false will not remove the
// .data("test") value
$('#test').renameAttr('data-test', 'data-new', false );

Basically, it adds a new attribute with the same value and removes the old one. It automatically removes the jQuery data for the original attribute unless you set the "removeData" flag to false. Check out this demo

Thursday, October 6, 2011

Adding Swipe Support

I spent some time trying to figure out how to add mobile swipe support without using jQuery Mobile, because the swipe is all I really needed. I don't own an iPhone/iPad/iAnything, so it took a bit of back-and-forth testing - thanks to all the folks over at CSS-Tricks.com - to get this to finally work.



Here is the complete code:

var maxTime = 1000, // allow movement if < 1000 ms (1 sec)
    maxDistance = 50,  // swipe movement of 50 pixels triggers the swipe

    target = $('#box'),
    startX = 0,
    startTime = 0,
    touch = "ontouchend" in document,
    startEvent = (touch) ? 'touchstart' : 'mousedown',
    moveEvent = (touch) ? 'touchmove' : 'mousemove',
    endEvent = (touch) ? 'touchend' : 'mouseup';

target
    .bind(startEvent, function(e){
        // prevent image drag (Firefox)
        e.preventDefault();
        startTime = e.timeStamp;
        startX = e.originalEvent.touches ? e.originalEvent.touches[0].pageX : e.pageX;
    })
    .bind(endEvent, function(e){
        startTime = 0;
        startX = 0;
    })
    .bind(moveEvent, function(e){
        e.preventDefault();
        var currentX = e.originalEvent.touches ? e.originalEvent.touches[0].pageX : e.pageX,
            currentDistance = (startX === 0) ? 0 : Math.abs(currentX - startX),
            // allow if movement < 1 sec
            currentTime = e.timeStamp;
        if (startTime !== 0 && currentTime - startTime < maxTime && currentDistance > maxDistance) {
            if (currentX < startX) {
                // swipe left code here
                target.find('h1').html('Swipe Left').fadeIn();
                setTimeout(function(){
                    target.find('h1').fadeOut();
                }, 1000);
            }
            if (currentX > startX) {
                // swipe right code here
                target.find('h1').html('Swipe Right').fadeIn();
                setTimeout(function(){
                    target.find('h1').fadeOut();
                }, 1000);
            }
            startTime = 0;
            startX = 0;
        }
    });


Now, lets break down what each line does. First, some definitions:

var maxTime = 1000, // allow movement if < 1000 ms (1 sec)
    maxDistance = 50,  // swipe movement of 50 pixels triggers the swipe

    target = $('#box'),
    startX = 0,
    startTime = 0,

The "maxTime" variable is a set to 1000 milliseconds. It is used to compare the touch start and touch end times, and if less than one second (1000 ms) it is considered to be a valid swipe, otherwise it is ignored. Basically, we don't like them slow swipers! Hehe, ok, actually, it to determine if the event ends up being a touch or a swipe.

"maxDistance" is the number of pixels from the touch start point that the mouse or a finger moved to determine if a swipe or touch is occurring.

The "target" variable, is the target element to add swipe support to. For the demo, I added an H1 tag inside of this element to add a swipe message. The "startX" and "startTime" variables are set to zero because the "touchmove" or "mousemove" event looks to see if they are set with the "startX" position and start time. If zero, it ignores the move.

In the next part we figure out of the device has touch support and set up the event names appropriately:

touch = "ontouchend" in document,
startEvent = (touch) ? 'touchstart' : 'mousedown',
moveEvent = (touch) ? 'touchmove' : 'mousemove',
endEvent = (touch) ? 'touchend' : 'mouseup';

The "touch" varible is true if the "ontouchend" event exists, which means touch is supported. All that line is doing is looking to see if that object exists.

The next variables, "startEvent", "moveEvent" and "endEvent" contain the necessary event names for start, move and end, respectively. Which the next lines of code will now bind to:
target
    .bind(startEvent, function(e){
        // prevent image drag (Firefox)
        e.preventDefault();
        startTime = e.timeStamp;
        startX = e.originalEvent.touches ? e.originalEvent.touches[0].pageX : e.pageX;
    })
The "touchstart" or "mousedown" event code contains the following:

"e.preventDefault()" prevents the image drag function in Firefox which actually makes you think you're dragging the image instead of swiping. Now we define "startTime" and "startX" as the start time and position of the swipe. The "startX" needs to get the position of the touch from the touches variable, but only the first one. We don't want to swipe if there are multiple touches (gestures) but it shouldn't work anyway since we're not binding to the gestures event.
.bind(endEvent, function(e){
    startTime = 0;
    startX = 0;
})
Now the "touchend" or "mouseup" event code only clears the start time and position variables. The reason for this, which was a fun and painful lesson, is that these events don't fire when your mouse/finger leaves the element or goes off the screen. So the actual swipe calculations are done in the move event functions.

.bind(moveEvent, function(e){
    e.preventDefault();
    var currentX = e.originalEvent.touches ? e.originalEvent.touches[0].pageX : e.pageX,
        currentDistance = (startX === 0) ? 0 : Math.abs(currentX - startX),
        // allow if movement < 1 sec
        currentTime = e.timeStamp;
    if (startTime !== 0 && currentTime - startTime < maxTime && currentDistance > maxDistance) {
        if (currentX < startX) {
            // swipe left code here
            target.find('h1').html('Swipe Left').fadeIn();
            setTimeout(function(){
                target.find('h1').fadeOut();
            }, 1000);
        }
        if (currentX > startX) {
            // swipe right code here
            target.find('h1').html('Swipe Right').fadeIn();
            setTimeout(function(){
                target.find('h1').fadeOut();
            }, 1000);
        }
        startTime = 0;
        startX = 0;
    }
});

The "touchmove" or "mousemove" events is where the main portion of this swipe code exists. Again, we want to prevent the default functionality of the move/drag. The "currentX" variable is set with the current x position of the finger/mouse. Again, the x position is obtained from the touches variable, if available. The difference "currentDistance" between the starting x and current x position is then determined. We need the absolute value of this number (ignore the negative sign if it is there) to see if it is more than the "maxDistance" variable. Also here is another trick to make sure the move event is occurring after a start event, check to make sure the starting x position isn't zero. If it is, set the "currentDistance" variable to zero. We also need determine the "currentTime" current time of the move event.

Now, we check to make sure the move event occurred after a start event. This can happen is the start event occurs outside of the element or window. So, first we make sure the "startTime" isn't zero, then we check that the time difference from start to current time is less than one second. And finally, we check that the current distance is more than the max distance setting. If it is, then we have a swipe!

YAY finally a swipe! Now to just figure out if it was going to the right or left. We do this by comparing the current position "currentX" to the starting position "startX"; if the current position is less than the start, then it's a swipe to the left and if it is greater, then the swipe was to the right.

Last but not least, we set the start time and position back to zero to prevent multiple firing of the swipe event.

I hope everything had a clear enough explanation. Please feel free to leave a comment or email me (gmail with wowmotty as the user) with questions or if you need further clarification.

Thursday, September 22, 2011

Print a Paragraph

By default, browsers will print the content of an entire page/web page or selected text. With some simple scripting you can make it easier for your users to get the browser to print the contents of a particular block or paragraph. The trick is to copy these contents into a new popup window, then print the contents of that window. I put together a very simple demo, which I should turn into a plugin, of how to do this:

Sunday, September 11, 2011

AnythingSlider Themes

I put together ten more themes for AnythingSlider! YAY!


  • All theme files are meant to be used independently from the plugin's "anythingslider.css" file. So you only need to include one of these theme files.
  • There is an additional stylesheet named "wrappers.css" which only targets the wrapper around the slider. It is independent of the theme files and meant to be used by extracting out any specific wrapper style you would like to use.
  • The first default theme is meant to be used as the base to make your own theme, but without using images or css3.
  • The second default theme is also meant to be used as a base for your own theme, but includes images (no css3 though)
  • The rest of the themes are free to use.
If you would like to contribute a theme, either fork the repository, add your theme then send me a pull request or just email me the files - my gmail account, user name is wowmotty.

Thanks and enjoy!

Wednesday, August 17, 2011

AnythingSlider FX Builder

So, to make it easier to figure out how to add FX to AnythingSlider, I put together a bookmarklet (get it from here) that allows you to build/play with the effects live :)



If you need instructions, I have some very basic information in the readme file on github.

Right now, it's still in beta and it only works on the first slider on the page. I'd appreciate any feedback on what could be improved, changed, or if you find any problems.

Tuesday, July 12, 2011

Adding WordPress Quicktag Buttons to a WP Plugin

Disclaimer: I am a total noob when it comes to WordPress and WordPress plugins.

I was wondering how difficult it would be to add a button to the WordPress Post Editor. I've seen a few plugins do it, but I discovered that they had to add their own version of TinyMCE to do it.

So I searched around on the internet, and found a few methods. But they either stopped working or required you to edit your quicktags.js file to work, then again after each Wordpress update. So I looked into it and I found a solution that works with the current version 3.2 and hopefully future versions without requiring you to edit or replace the quicktags file.

Before

After

Note: This code is intended to be added inside your WordPress plugin. Like I said, I'm a total noob with WordPress so I'm not sure if it would work outside of a plugin.

CSS - This CSS should be outside of the php tags
<style>
/* Simulate the input buttons styles */
#ed_toolbar button {
 display: inline-block;
 vertical-align: middle;
 margin: 0;
 padding: 0;
 min-width: 26px;
 height: 24px;
 -moz-border-radius: 3px;
 -webkit-border-radius: 3px;
 border-radius: 3px;
 border: #c3c3c3 1px solid;
 background: url() repeat-x;
}
#ed_toolbar button:hover {
 border: #aaa 1px solid;
 background: #ddd;
}
/* toolbar button images in span */
#ed_toolbar button span {
 display: inline-block;
 background: transparent no-repeat center center;
 padding: 4px 2px;
 width: 16px;
 height: 16px;
}

/* toolbar custom button image */
#_custom span {
 background-image: url(/wp-content/plugins/my-plugin-directory/my-plugin-icon.png);
}
</style>

PHP - Add this to your plugin php
<?php
add_action('admin_footer', 'my_plugin_add_button');

function my_plugin_add_button(){
echo <<<EOT
<script>/* <![CDATA[ */
var j, n,
 last = edButtons.length,
 tbar = '';

// add a new editor button as follows:
// edButtons[edButtons.length] = new edButton('BUTTON ID', 'BUTTON TEXT', 'OPENING TAG', 'CLOSING TAG', 'ACCESSKEY');
// Example: edButtons[edButtons.length] = new edButton('_h1', 'H1', '<h1>', '</h1>', -1);
edButtons[edButtons.length] = new edButton('_h1', 'h1', '<h1>', '</h1>', "h");
edButtons[edButtons.length] = new edButton('_h2', 'h2', '<h2>', '</h2>', -1);
edButtons[edButtons.length] = new edButton('_h3', 'h3', '<h3>', '</h3>', -1);
edButtons[edButtons.length] = new edButton('_h4', 'h4', '<h4>', '</h4>', -1);
edButtons[edButtons.length] = new edButton('_h5', 'h5', '<h5>', '</h5>', -1);
edButtons[edButtons.length] = new edButton('_h6', 'h6', '<h6>', '</h6>', -1);
edButtons[edButtons.length] = new edButton('_custom', '<button>', '[myplugin_short_code]', '', -1);

// Don't change anything below
// ***************************
for ( j = last; j < edButtons.length; j++) {
 n = edButtons[j];
 if (/<button>/g.test(n.display)) {
  // matched opening tag => add a button
  tbar += '<button id="' + n.id + '" type="button" class="ed_button" onclick="edInsertTag(edCanvas, ' + j + ');"><span></span></button>';
 } else {
  // add an input
  tbar += '<input type="button" id="' + n.id + '" accesskey="' + n.access + '" class="ed_button" onclick="edInsertTag(edCanvas,' + j + ');" value="' + n.display.replace(/\"/g,'\"') + '" />';
 }
}
document.getElementById('ed_toolbar').innerHTML += tbar;
/* ]]> */</script>
EOT;
}
?>

Saturday, June 18, 2011

jQuery Tablesorter, The Missing Docs

If you need support for my fork of tablesorter, please contact me here:

The Missing Docs for jQuery Tablesorter 2.0

These docs have been incorporated into my

All Defaults


Appearance
cssHeader: "header"The CSS style used to style the header in its unsorted state.
cssAsc: "headerSortUp"The CSS style used to style the header when sorting ascending.
cssDesc: "headerSortDown"The CSS style used to style the header when sorting descending.
cssChildRow: "expand-child"This allows you to add a child row that is always attached to the parent. See the children rows section below.
widgetZebra: { css: ["even", "odd"] }Set the CSS classes used for zebra striping the table. See the updating zebra striping section below.
widthFixed: falseIndicates if tablesorter should apply fixed widths to the table columns. This is useful for the Pager companion. Requires the jQuery dimension plugin to work. See the main demo page
onRenderHeader: nullThis function is called when classes are added to the th tags. You can use this to append HTML to each header tag. See the Render Header section below.
Sort Options
sortInitialOrder: "asc"A string of the inital sorting order can be "asc" or "desc". This is overridden bt the sortList settings.
sortForce: nullUse to add an additional forced sort that is prepended to sortList. For example, sortForce: [[0,0]] will sort the first column in ascending order. See the main demo page
sortList: []Add an array of the order the table should be initially sorted; e.g. sortList: [[0,1], [1,0]]. The first part "[0,1]" will sort the first column (zero based index) in decending order and the second part "[1,0]" is to sort the second column in ascending order. See the main demo page.
sortAppend: nullUse to add an additional default sorting rule, that is appended to the sortList.
Parsers & Widgets
headers: {}See the Change how you sort a column section below or see the main demo page
textExtraction: "simple"See the Text Extraction section below or see the main demo page
sortLocaleCompare: trueBoolean flag indicating whenever to use String.localeCampare method or not. This is only used when comparing text strings.
parsers: {}Internal list of all of the parsers. See a complete list of parsers below.
widgets: []Initialize widgets using this option (e.g. widgets : ['zebra'], or custom widgets widgets: ['zebra', 'myCustomWidget']; see this demo on how to add a custom widget.)
selectorHeaders: 'thead th'jQuery selectors used to find the header cells. You can change this, but the table will still need the required thead and tbody before this plugin will work properly.
headerList: []Internal list of each header element as selected using jQuery selectors in the selectorHeaders option.
dateFormat: "us"Set the date format. See the dataFormat section below.
Interaction
sortMultiSortKey: "shiftKey"The key used to select more than one column for multi-column sorting. Defaults to the shift key. Other options might be ctrlKey, altKey. See the main demo page
cancelSelection: trueIndicates if tablesorter should disable selection of text in the table header (TH). Makes header behave more like a button.
Unknown
decimal: '/\.|\,/g'Regex to find the decimal "." for U.S. format and "," for European format... but I can't find it being used anywhere in the plugin.
Debugging
debug: falseBoolean flag indicating if tablesorter should display debuging information usefull for development. This displays information in the console, if available, or in alerts. See the main demo page

Update Default Settings

Update any of the default settings above using this format:
$(selector).data("tablesorter").{default name} = newValue;
Here are a few examples:
// Change sortForce
$("table").data("tablesorter").sortForce = [[1,0]];
// it appears that the only way to update the table is to resort it
$("table").trigger("sorton", [[0,1]]);
// Add zebra sorting after the table has initialized
$("table").data("tablesorter").widgets = ["zebra"];
// there is a method to apply widgets!
$("table").trigger("applyWidgets");

Child Rows

To add a row that remains attached to a parent row, add the "expand-child" class to the row.

You can add some scripting to hide these child rows and have them expand when you click on a link; this is the original reason for the css name.

Here is the original mod page, the code was added into the TableSorter core but remains undocumented.

This makes the tablesorter plugin keep these rows together. This css class can be changed using the "cssChildRow" option.

Here is some example markup:
<table width="100%" border="1">
  <thead>
    <tr>
      <th>Item #</th>
      <th>Name</th>
      <th>Available</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>12345</td>
      <td>Toy Car</td>
      <td>5</td>
    </tr>
    <tr class="expand-child"> <!-- this row will remain attached to the above row, and not sort separately -->
      <td colspan="3">
        It's a toy car!
      </td>
    </tr>
    <tr>
      <td>23456</td>
      <td>Toy Plane</td>
      <td>2</td>
    </tr>
    <tr class="expand-child"> <!-- this row will remain attached to the above row, and not sort separately -->
      <td colspan="3">
        It's a toy plane!
      </td>
    </tr>
    <tr class="expand-child"> <!-- this row will remain attached to the above two rows, and not sort separately -->
      <td colspan="3">
        and it flies!
      </td>
    </tr>
  </tbody>
</table>


Update Zebra Striping After Sorting

Actually the zebra striping widget does this for you automatically.

By default the odd and even rows are styled using "odd" and "even" class names ({ css: [ "even", "odd" ] }).

Change these css class names using the widgetZebra option as follows:
$(function(){
  $("table").tablesorter({
    widgets: ["zebra"], // initialize zebra striping of the table
    widgetZebra: { css: [ "normal-row", "alt-row" ] }
  });

Modify the Header Markup

The onRenderHeader option allows you to add a function that which can modify the HTML of each header tag. In the example below, the header cell (th) content is wrapped with a span tag to allow for better styling (source).
$(function(){
  $("table").tablesorter({
    onRenderHeader: function (){
      this.wrapInner('<span class="roundedCorners"></span>');
    }
  });
});
and you'll end up with this (only the thead is shown):
<thead>
  <tr>
    <th class="header"><span class="roundedCorners">Column 1</span></th>
    <th class="header"><span class="roundedCorners">Column 2</span></th>
  </tr>
</thead>


Change how you sort a column

The plugin attempts to detect the type of data that is contained in a column, but if it can't figure it out then it defaults to alphabetical.

You can easily override this by setting the header argument (or column parser).
$(function(){
  $("table").tablesorter({
    headers: {
      0: { sorter: false },      // disable first column
      1: { sorter: "digit" },    // sort second column numerically
      4: { sorter: "shortDate" } // sort the fifth column by date (e.g. mm/dd/yyyy if the date format is "us")
    }
  });
});
*Note* the header number starts with zero (zero based index).

Here is a list of available sorter values/parsers:

sorter: falsedisable sort for this column.
sorter: "text"Sort alphabetically.
sorter: "digit"Sort numerically.
sorter: "currency"Sort by currency value (supports "£$€").
sorter: "ipAddress"Sort by IP Address.
sorter: "url"Sort by url.
sorter: "isoDate"Sort by ISO date (YYYY-MM-DD or YYYY/MM/DD).
sorter: "percent"Sort by percent.
sorter: "usLongDate"Sort by date (U.S. Standard, e.g. Jan 18, 2001 9:12 AM).
sorter: "shortDate"Sort by a shorten date (see "dateFormat").
sorter: "time"Sort by time (23:59 or 12:59 pm).
sorter: "metadata"Sort by the sorter value in the metadata - requires the metadata plugin.

Change the Date Format (dateFormat)

"us""mm-dd-yyyy" or "mm/dd/yyyy"
"uk""dd-mm-yyyy" or "dd/mm/yyyy"
"dd/mm/yy" or
"dd-mm-yy"
Sort by short year (it appears to sort by day first, not the year)




Change the Sorted Columns

There are three options to determine the sort order and this is the order of priority:
  1. sortForce forces the user to have this/these column(s) sorted first (null by default).
  2. SortList is the initial sort order of the columns.
  3. SortAppend is the default sort that is added to the end of the users sort selection (null by default).
The value of these sort options is an array of arrays and can include one or more columns. The format is an array of instructions for per-column sorting and direction in the format: [[columnIndex, sortDirection], ... ] where columnIndex is a zero-based index for your columns left-to-right and sortDirection is 0 for Ascending and 1 for Descending. A valid argument that sorts ascending first by column 1 and then column 2 looks like: [[0,0],[1,0]]
$(function(){
  $("table").tablesorter({
    sortForce  : [[0,0]],        // always sort first column first
    sortList   : [[1,0], [2,0]], // initial sort columns (2nd and 3rd)
    sortAppend : [[3,0]]         // always add this sort on the end (4th column)
  });
});
*NOTE* When writing this post, I didn't realize that the sortAppend option was listed as an option, but the code for it did not exist. This problem is fixed in my fork of tablesorter (example).

Text Extraction

This is demonstrated in the Dealing with markup inside cells demo page, but the example uses vanilla javascript to find the desired text, like this:
$(function(){
  $("table").tablesorter({
    textExtraction: function(node) {
      return node.childNodes[0].childNodes[0].innerHTML;
    }
  });
});
But you could make your life easier and just make the node into a jQuery object and then "find" what you need:
$(function(){
  $("table").tablesorter({
    textExtraction: function(node) {
      return $(node).find("span:last").text();
    }
  });
});
Now if the text you are finding in the script above is say a number, then just include the headers sorter option to specify how to sort it. Also in this example, we will specify that the special textExtraction code is only needed for the second column ("1" because we are using a zero-based index). All other columns will ignore this textExtraction function.


*NOTE* When writing this post, I didn't realize that the textExtraction option did not allow defining it per column, as shown below, but this has been fixed in the forked github version - example.
$(function(){
  $("table").tablesorter({
    textExtraction: {
      1: function(node) {
           return $(node).find("span:last").text();
      }
    },
    headers: {
      1: { sorter : "digit" }
    }
  });
});


Methods

See examples of each further down
sortonResort the table using new sort parameters.
updateUpdate the stored tablesorter data and the table.
appendCacheUpdate a table that has had its data dynamically updated; used in conjunction with "update"
updateCellUpdate a table cell in the tablesorter data.
applyWidgetIdApply a widget (wrapped in square brackets) to the table one time only.
applyWidgetsApply previously selected widgets to the table - will update the widget with new sort.

Resort the table - "sorton" method

See the full example in the tablesorter docs: Sort table using a link outside the table
// Choose a new sort order
var sort = [[0,0],[2,0]];
// Note that the sort value below is inside of another array (inside another set of square brackets)
$("table").trigger("sorton", [sort]);

Update the Table

See the full example in the tablesorter docs: Appending table data with ajax
// Add new content
$("table tbody").append(html);
// let the plugin know that we made a update
$("table").trigger("update");
// set sorting column and direction, this will sort on the first and third column
var sorting = [[2,1],[0,0]];
// sort on the first column
$("table").trigger("sorton", [sorting]);

Append Cache

If you dynamically change the table content, more than just one cell like in the "updateCell" example above, you may possibly have to trigger two events: "update" and "appendCache".

This answer is from a StackOverflow answer
// Table data was just dynamically updated
$(table)
  .trigger("update")
  .trigger("appendCache");

Update a Table Cell

Example from these alternate tablesorter docs: Updating the table cache - the demo doesn't work, but I've tested the example code below and it works properly.
$(function() {
  $("table").tablesorter();
  $("td.discount").click(function(){
      // randomize a number
      var discount = '$' + Math.round(Math.random() * Math.random() * 100) + '.' + ('0' + Math.round(Math.random() * Math.random() * 100)).slice(-2);
      $(this).text(discount);
      // update the table, so the tablesorter plugin knows its value
      $("table").trigger("updateCell",[this]);
      // set sorting column and direction, this will sort on the first and third column
      var sorting = [[3,1]];
      // sort on the first column
      $("table").trigger("sorton",[sorting]);
      return false;
  });
});
<table class="tablesorter" cellspacing="1">
  <thead>>
    <tr>
      <th>first name</th>
      <th>last name</th>
      <th>age</th>
      <th>total</th>
      <th>discount</th>
      <th>date</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>peter</td>
      <td>parker</td>
      <td>28</td>
      <td class="discount">$9.99</td>
      <td>20%</td>
      <td>jul 6, 2006 8:14 am</td>
    </tr>
    <tr>
      <td>john</td>
      <td>hood</td>
      <td>33</td>
      <td class="discount">$19.99</td>
      <td>25%</td>
      <td>dec 10, 2002 5:14 am</td>
    </tr>
    <tr>
      <td>clark</td>
      <td>kent</td>
      <td>18</td>
      <td class="discount">$15.89</td>
      <td>44%</td>
      <td>jan 12, 2003 11:14 am</td>
    </tr>
    <tr>
      <td>bruce</td>
      <td>almighty</td>
      <td>45</td>
      <td class="discount">$153.19</td>
      <td>44%</td>
      <td>jan 18, 2001 9:12 am</td>
    </tr>
    <tr>
      <td>bruce</td>
      <td>evans</td>
      <td>22</td>
      <td class="discount">$13.19</td>
      <td>11%</td>
      <td>jan 18, 2007 9:12 am</td>
    </tr>
  </tbody>
</table>

Apply a New Widget "applyWidgetId"

Apply a widget to the table. In this example, we apply the zebra striping to the table after it has been initialized.
$(function(){
  // initialize tablesorter without the widget
  $("table").tablesorter();
  
  // click a button to apply the zebra striping
  $("button").click(function(){
    $('table').trigger('applyWidgetId', ['zebra']);
});

Apply Chosen Widgets "applyWidgets"

Reapply a widget to the table. In this example, we apply the zebra striping to the table after it has been initialized.

This is basically an alternate method to the "applyWidgetId", but in the example below you can add or remove widgets from the chosen list.
// Update the list of widgets to apply to the table (add or remove)
$("table").data("tablesorter").widgets = ["zebra"];
$("table").trigger('applyWidgets');




Triggered Events

sortStartThis event fires when the tablesorter plugin is about to start resorting the table.
sortEndThis event fires when the tablesorter plugin has completed resorting the table.


Bind to the "sortStart" and/or the "sortEnd" events as shown on the example page
$(function(){
  // initialize the tablesorter plugin
  $("table").tablesorter();
  // bind to sort events
  $("table")
    .bind("sortStart",function() {
      $("#overlay").show();
    })
    .bind("sortEnd",function() {
      $("#overlay").hide();
    });
});
Or here is another example from StackOverflow
$(function(){
  $("table")
    .tablesorter()  
    .bind("sortEnd", function(){  
      $("table tr").removeClass("block");
      // adds the "block" class to every 8th row
      $("table tr:nth-child(8)").addClass("block");
    });
});

Monday, June 6, 2011

Equalizer

This script was first written by Stephen Akins to equalize column heights in multiple rows. Chris Coyier of CSS Tricks then posted an update which allowed the heights to adjust while resizing a page. I simply debugged, added a few improvements (like minimum and maximum height, an idea from Mike Avello's plugin) and turned it into a plugin.


In the screen shot above, there are three blocks containing three columns. The three blocks have identical HTML. The left block is unmodified. The center block has Equalizer applied with a max height setting; and each block that exceeds the max height has a css class applied which sets the overflow to auto and adds the different background color for the demo, but you can do whatever you want via the css. And the right block has Equalizer applied with a minimum height setting; you should be able to see in the screen shot that the middle row of the right block has a minimum height set.

Try out the demo, resize your browser window and see how everything changes.

Wednesday, May 18, 2011

The Base Tag

Whenever someone asks me to help troubleshoot a problem with their site, if I can't immediately see the problem or fix it using Firebug, I download the page as an HTML only file. Then you have to deal with pages that have relative links to code, style sheets or images:
<link href="/css/main.css" rel="stylesheet">
which you will probably have to use your editor to find and replace each one. A big time saver is using the apparently less known base tag, in which you add the base URL to the site.
<base href="http://some-site.com">
Then if you need to modify a file for testing, just download it to your computer (e.g. your desktop), open it in your browser, then copy the URL. Replace the file's url with this one, since the relative URL will look to the base tag instead of your desktop:
<link href="file:///C:/Desktop/main.css">
Now when you open the page in your browser, it should look exactly like the online page.  :)

Sunday, May 15, 2011

jQuery find/replace text without destroying DOM elements

I found this question on StackOverflow on how to replace text without destroying event handlers, DOM nodes and such. He just wanted to replace a name inside a block of HTML. The name could be inside of an LI, A (link), p or div element, etc. Here is the example HTML markup provided:
<div id="test">
    <h1>An article about John</h1>
    <p>The first paragraph is about John.</p>
    <p>The second paragraph contains a <a href="#">link to John's CV</a>.</p>
    <div class="comments">
        <h2>Comments to John's article</h2>
        <ul>
            <li>Some user asks John a question.</li>
            <li>John responds.</li>
        </ul>
    </div>
</div>
You couldn't simply grab the html() and replace the text because it the result replaces all of the elements inside of "#test" and thus breaks all previously attached functions (click, hover, etc.) or any entered form data inside of inputs (demo):
$('#test').html(function(i, v) {
    return v.replace(/John/g, 'Peter');    
});
Instead you'd have to step through each text node, find the name, then replace it - it's quite a chunk of code. But I think I found a quick and dirty solution (demo) that works on the HTML markup above:
$('#test :not(:has(*))').text(function(i, v) {
  return v.replace(/John/g, 'Peter');    
});
What this does is find all elements that don't have children, and replace the text inside.

To understand what the selector is doing, first imagine the selector like this: "#test *:not(:has(*))". So reading it out in plain language would sound like this: Find the element with an ID of test, then find all elements within it (the first asterisk "*"), then find the opposite ":not()" of all elements that have children ":has(*)", meaning elements that don't have children. Then get the text and replace it.

But this quick and dirty method does have an important limitation which is that it would ignore text inside an element that has child elements. Here is an example of what I mean where the link is treated as a child element and thus the name is not replaced (nothing would change):
<div id="test">
    <p>Visit John's <a href="blog.html">blog</a></p>
</div>
So, if you do have markup like as it is above, then just wrap the name in a span (demo):
<div id="test">
    <p>Visit <span>John's</span> <a href="blog.html">blog</a></p>
</div>
If you don't or can't wrap the name, then you can't use the quick and dirty method I posted above. You'll have to use this textWalk plugin by PatrickDW (a demo, and see his answer for full details) or check out Bobince's answer in this question for a similar method of walking through the text nodes.

Monday, May 2, 2011

FancySelector

I just created FancySelector which replaces the standard select with a more visually appealing select. It allows you to scroll through the options with either the keyboard or mouse. Selecting multiple options is similar but not exactly the same as using a standard select. Please see the documentation for more details.

There are two demos, a full screen demo (pictured below) and an inline selector demo.


This jQuery plugin is available for download on github.

Saturday, April 23, 2011

Visual Event Bookmarklet

I've been using Visual Event bookmarklet to help quickly visualize which events are attached to a page while troubleshooting problems.

I do have one problem with the script. It adds too many layers so you can't get to the lower layers. Here is a screen shot of how the last post I made looks with Visual Event (on the left) and the second screen shot is how it looks after the script is applied (on the right):

I made a github gist and a jsFiddle demo of this script. To use it, just add the following script into the Firebug console to enable the script:
var veColors = [ 'black', 'orange', 'purple', 'green', 'blue', 'yellow', 'red' ],
 veColorLength= veColors.length - 1,
 veLayerIndex = 0;
 
function showVeLayer(nxt){
 veLayerIndex += (nxt) ? 1 : -1;
 if (veLayerIndex > veColorLength) { veLayerIndex = 0; }
 if (veLayerIndex < 0) { veLayerIndex = veColorLength; }

 var veLayer =  $('.Event_bg_' + veColors[veLayerIndex]);
 if (veLayer.length === 0 ) { showVeLayer(nxt); return; }

 $('.Event_bg_' + veColors.join(', .Event_bg_')).hide();
 veLayer.show();
};

$(document).keyup(function(e){
 switch(e.which){
  case 39: case 40: // right/down
   showVeLayer(true);
   break;
  case 37: case 38: // left/up
   showVeLayer();
   break;
 }
});

FullCalendar mini-sized

I've always liked Adam Shaw's FullCalendar plugin and while I was trolling Stackoverflow I found a question about how one would make a tiny version of the calendar. Gears started grinding and I figured that it could be done with just some basic CSS. YAY!!

This is from the answer I posted on Stackoverflow:
You can make a fully functional tiny version by adding a bit of CSS. I had to add a "eventMouseover" callback to add the event name to the title attribute, so you can see it's name in the tooltip.

Here is a screen shot of the mini-sized calendar (200 x 225)


And jsFiddle for a demo:



So, just in case something happens to Stackoverflow and/or jsFiddle, here is the CSS and javascript that was used:
#calendar {
    width: 200px;
    margin: 0 auto;
    font-size: 10px;
}
.fc-header-title h2 {
    font-size: .9em;
    white-space: normal !important;
}
.fc-view-month .fc-event, .fc-view-agendaWeek .fc-event {
    font-size: 0;
    overflow: hidden;
    height: 2px;
}
.fc-view-agendaWeek .fc-event-vert {
    font-size: 0;
    overflow: hidden;
    width: 2px !important;
}
.fc-agenda-axis {
    width: 20px !important;
    font-size: .7em;
}

.fc-button-content {
    padding: 0;
}
$(document).ready(function() {

    $('#calendar').fullCalendar({
        theme: true,
        header: {
            left: 'prev,next today',
            center: 'title',
            right: 'month,agendaWeek,agendaDay'
        },
        editable: true,

        // add event name to title attribute on mouseover
        eventMouseover: function(event, jsEvent, view) {
            if (view.name !== 'agendaDay') {
                $(jsEvent.target).attr('title', event.title);
            }
        }
    });

});

Tuesday, April 19, 2011

Vertical Parallax Effect for the Entire Page

I found a game site (Elder Scrolls) that has a really cool vertical scrolling effect that I wanted to duplicate, but it only works in webkit browsers. I didn't make it into a plugin, but instead made this tutorial, because if you understand the basics, you can add more background images or use CSS3 to add multiple background images.



This is a screen shot of the web page while it is at the top. Note the location of the building tower top is near the top of the logo that is under "Generic".


This screen shot shows how the top of "Box 1" is now above the building tower. Blocks in the foreground move slightly faster than the background image.


Here is the final demo of the vertical scrolling effect (see it as a full page).

How it's done

The Images
This effect uses two background images. The top image (bg1.jpg, 1920x1080 pixels) fades to solid black on its bottom edge.

It's made to be a bit blurry, but you can duplicate the game site's background image by keeping the foreground sharp and the background blurry... it's a cooler effect ;)
 

The second background image (bgtile.jpg, 963x1641 pixels) is added as a background image that appears under the above top image. The css is adjusted so that the top of this image appears below the top image so that you don't see a straight black line cut through the top.

This image is blurry and mostly black, it fades to solid black at the edges, with a rock-like texture. It makes the vertical parallax scroll very subtle, so you may want to adjust it as desired.


The other two images that are used are the title/logo image (title.png, 400x300 pixels).



And the box background (bg-black-55.png, 20x20 pixels) which is a png file of solid black with a 55% opacity.


The Markup
This is very basic HTML markup. The body contains the repeating background tile image (bgtile.jpg). A wrapper is immediately inside the body which contains the top background image (bg1.jpg). These two images could be combined in the body tag if you use css3; then you could use the wrapper for additional images. The Header contains the logo (title.png) and each block of content has the box background image (bg-black-55.png) applied. Fill in the "div.content" block with whatever you want.
<body> <!-- contains repeated background image -->
<div id="wrapper"> <!-- contains top image -->

<div id="header"></div> <!-- contains the page title image -->

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 1</h3>
  <div class="content">Content 1.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 2</h3>
  <div class="content">Content 2.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 3</h3>
  <div class="content">Content 3.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 4</h3>
  <div class="content">Content 4.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 5</h3>
  <div class="content">Content 5.</div>
 </div>

</div>

</body>

The CSS
The CSS below doesn't have a lot of comments, but the important parts to focus on are the main background image positions.
body {background: #333; color: #ddd;}
a:link { color: #ddd; }
a:hover { text-decoration: underline; }
a:visited,a:active { color: #999; }

/* Tiled background image */
body {
 margin: 0;
 padding: 0;
 /* Use height of header image for top position */
 background: #000 url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7fBBufKSR47RD9ESntGl85BAeIpSrYvQv9JzbnrK6i-jYhidex39OZ6_4lDD3EXxKv7I3pd4XWnOUyzax9evROh_GLcIfkX8I3vQO6Y1FuA0KruXm33MggzrqsSVR91iOFJvTZylHFiM/s200/bgtile.jpg) left 1080px repeat-y;
}
/* Top background image (1920x1200) */
#wrapper {
 position: relative;
 top: 0;
 left: 0;
 height: 100%;
 width: 100%;
 background: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizCx7mqIPhdQ10Xgmieaa4PTAR5suXGEi7ezqiiL5G_h0yFCXhvibhec2mTLDBXGnkTbWC-LeqbKT634Ppt40wdciHfm6fq7QuY5v1pDeWJ6nvzv8T7bj3mrOBd-oxDGuhJmIc5daHeA0/s200/bg1.jpg) center top repeat-x;
 z-index: 100;
}
/* Page Title image */
#header {
 height: 350px;
 background: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipx4m-HiIJ90H0QrnLtd5DHyP1J29su3Sn1McbkRcQrKu934Z6WjSGM7stMU5RB97-ZVrQ4_zHV7r81NuIXWmTdXIkoNyuTCj19sGjfxGDkLfOpL0Ou_1jE4xWB-NS5WnP_svT4OkqhbQ/s200/title.png) center 40px no-repeat;
}
/* Content Block with 55% opacity background image */
.block {
 width: 400px;
 height: 500px;
 margin: 20px auto;
 border: #333 1px solid;
 padding: 20px;
 background: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj82Brf2wM1QpoBT96gFD5ZXiJFpFTBHxKF4ghGPJwMMkkSF_ifPi4PMFM20c5ravbOTe1lhOLzrK0TDmV0Vq_wys_x09oF3VTSPh_sOrup0B5xc7CRR3tqh9zHm3DvQRIL2maSeryZ2us/s1600/bg-black-55.png);
}
.block h3 {
 font-family: 'Arial Black', Gadget, sans-serif;
 font-size: 130%;
}

The Script
jQuery(document).ready(function(){
  // saved values
  var st, 
   // get window and body elements
   win = jQuery(window)[0],
   body = jQuery('body')[0],
   wrap = jQuery('#wrapper')[0],
   // make sure we target the correct document (in IE)
   doc = (jQuery.support.boxModel) ? document.documentElement : document.body,
  
   // Set top background image height here, in pixels
   imgH = 1080;

  // vertical parallax scroll code
  jQuery(win).scroll(function(){
   // get the page scroll top (IE uses doc.scrollTop)
   st = (win.pageYOffset || doc.scrollTop );

   // Limit moving top image (in the "wrap") only when in view (st < imgH)
   // Doing this reduces the amount of DOM activity so it doesn't slow down the scrolling.
   // Here is where the background position of the top image is moved by 1/4 (st/4) of the total amount scrolled
   // Increasing the fraction (making it closer to one) will speed up the background parallax scroll
   // Decreasing the fraction will slow down the parallax scroll
   if (st < imgH) { wrap.style.backgroundPosition = 'center ' + (st/4) + 'px'; }

   // This line limits the tiled background image in the "body"
   body.style.backgroundPosition = 'left ' + ( imgH + st/4) + 'px';
  });
});
Code breakdown
  • The first and last line wrap the code which is run when the document is ready. This is a jQuery method and can be written several different ways. Basically, any code inside is run when the basic structure and code of the page has completed loading. Images may or may not have completed loading at this time.
    jQuery(document).ready(function(){
      // code to run
    });
  • The next few lines save the variables that are repeatedly used in the script. Saving them reduces the amount of time taken to run code inside the scroll function. See the optimization section for more details.
    // saved values
    var st, 
     // get window and body elements
     win = jQuery(window)[0],
     body = jQuery('body')[0],
     wrap = jQuery('#wrapper')[0],
     // make sure we target the correct document (in IE)
     doc = (jQuery.support.boxModel) ? document.documentElement : document.body,
    
    *Note that each line ends with a comma - it is defining multiple variables with one "var".

  • The next saved variable saves the top background image height (bg1.jpg). This is needed so the script can figure out where to start the tiling of the repeated background image (bgtile.jpg). This ensures that the tiled background image isn't overlapped by the top background image making the user see a sharp black edge at the overlap. The script also uses this height to stop the parallax scroll of the top background image when it isn't in view. The reason for this is discussed in the optimization section below.
    // Set top background image height here, in pixels
    imgH = 1080;
  • Now we bind to the window scroll event. This event is fired everytime the browser window is scrolled. So it can be called hundreds of times while scrolling down the page. This is the reason the code inside this function is kept as minimal as possible.
    // vertical parallax scroll code
    jQuery(win).scroll(function(){
    
      // code run each time the page is scrolled
    
    }); // second to last line in the completed script
  • Get the scroll top of the page. Check standard browsers variable, and if not defined (IE) then get the document scroll top.
    // get the page scroll top (IE uses doc.scrollTop)
    st = (win.pageYOffset || doc.scrollTop );
  • Apply the parallax to the top background image. 
    // Here is where the background position of the top image is moved by 1/4 (st/4) of the total amount scrolled
    // Increasing the fraction (making it closer to one) will speed up the background parallax scroll
    // Decreasing the fraction will slow down the parallax scroll
    if (st < imgH) { wrap.style.backgroundPosition = 'center ' + (st/4) + 'px'; }
    • The first thing we do is check if the scroll top (st) is less than the top background image height. If it is, then use parallax on the top background image.
    • "wrap" is one of our saved variables that points to the page wrapper. Here we set the background position using direct DOM manipulation. See optimization section for more details.
    • The background position has two parts - "X Y" positions. We care centering the image from left to right (the X-position), then taking one fourth (1/4) of the scroll top position and making that the background's Y-position. This is basically how the parallax scroll works. It scroll 1/4 less than the actual page. If you add another layer (say wrapper 2), it should be scrolled slightly more (a number closer to one, like 2/3).
    • So here the top background image is scrolled 1/4 less (1/4 * st) than the page itself. Make this number closer to one (i.e. 3/4 * st) to make the scroll more like the page scroll. Or make the number further from one (i.e. 1/8 * st) to make the scroll a tiny bit. Making the number too small may make the background image appear fixed, so don't go too crazy ;)
  • Apply the parallax to the tiled background image.
    // This line limits the tiled background image in the "body"
    body.style.backgroundPosition = 'left ' + ( imgH + st/4) + 'px';
    • Now we are applying the background position to the body of the page, where the tiled background image (bgtile.jpg) is seen.
    • Here we are place the background image on the left edge (centering should be fine too) and again 1/4 of the scroll top of the page. In addition we add the top image height (imgH) to keep the tiled background image aligned to the bottom of the top background image. So we make sure not to see that black image edge.
    • Since the body background image is behind the wrapper background image, you could make this parallax scroll even slower to give more of a feel of it being further off in the distance. This would look especially good with multiple background images available when using css3.
Optimizations
  • This script was optimized so that a minimal amount of code is run inside the scroll function.
  • This ensures that the code doesn't slow down the page scroll, because the scroll function is called constantly. It's probably not a big deal is modern browsers, but is very noticeable in older browsers. 
  • This was done, in part, by saving variables. This limits the amount of look ups that need to be done by the script. Using $(window) or $(document) needs to call several functions to get a value, so saving it in a variable saves time.
  • To set the background position, the script sets the background position directly in the DOM. Again, this was done to minimize function calls. I didn't have any trouble with cross browser compatibility in any browsers, including Opera and IE7+.