﻿// version 1.4.0
// http://welcome.totheinter.net/columnizer-jquery-plugin/
// created by: Adam Wulf adam.wulf@gmail.com

(function($) {

  $.fn.columnize = function(options) {


    var defaults = {
      // default width of columnx
      width: 400,
      // optional # of columns instead of width
      columns: false,
      // true to build columns once regardless of window resize
      // false to rebuild when content box changes bounds
      buildOnce: false,
      // an object with options if the text should overflow
      // it's container if it can't fit within a specified height
      overflow: false,
      // this function is called after content is columnized
      doneFunc: function() { },
      // if the content should be columnized into a 
      // container node other than it's own node
      target: false,
      // re-columnizing when images reload might make things
      // run slow. so flip this to true if it's causing delays
      ignoreImageLoading: true,
      // should columns float left or right
      float: "left",
      // ensure the last column is never the tallest column
      lastNeverTallest: false
    };
    var options = $.extend(defaults, options);

    return this.each(function() {
      var $inBox = options.target ? $(options.target) : $(this);
      var maxHeight = $(this).height();
      var $cache = $('<div></div>'); // this is where we'll put the real content
      var lastWidth = 0;
      var columnizing = false;
      $cache.append($(this).children().clone(true));

      // images loading after dom load
      // can screw up the column heights,
      // so recolumnize after images load
      if (!options.ignoreImageLoading && !options.target) {
        if (!$inBox.data("imageLoaded")) {
          $inBox.data("imageLoaded", true);
          if ($(this).find("img").length > 0) {
            // only bother if there are
            // actually images...
            var func = function($inBox, $cache) {
              return function() {
                if (!$inBox.data("firstImageLoaded")) {
                  $inBox.data("firstImageLoaded", "true");
                  $inBox.empty().append($cache.children().clone(true));
                  $inBox.columnize(options);
                }
              } 
            } ($(this), $cache);
            $(this).find("img").one("load", func);
            $(this).find("img").one("abort", func);
            return;
          }
        }
      }

      $inBox.empty();

      columnizeIt();

      if (!options.buildOnce) {
        $(window).resize(function() {
          if (!options.buildOnce && $.browser.msie) {
            if ($inBox.data("timeout")) {
              clearTimeout($inBox.data("timeout"));
            }
            $inBox.data("timeout", setTimeout(columnizeIt, 200));
          } else if (!options.buildOnce) {
            columnizeIt();
          } else {
            // don't rebuild
          }
        });
      }

      /**
      * return a node that has a height
      * less than or equal to height
      *
      * @param putInHere, a dom element
      * @$pullOutHere, a jQuery element
      */
      function columnize($putInHere, $pullOutHere, $parentColumn, height) {
        while ($parentColumn.height() < height &&
				  $pullOutHere[0].childNodes.length) {
          $putInHere.append($pullOutHere[0].childNodes[0]);
        }
        if ($putInHere[0].childNodes.length == 0) return;

        // now we're too tall, undo the last one
        var kids = $putInHere[0].childNodes;
        var lastKid = kids[kids.length - 1];
        $putInHere[0].removeChild(lastKid);
        var $item = $(lastKid);


        if ($item[0].nodeType == 3) {
          // it's a text node, split it up
          var oText = $item[0].nodeValue;
          var counter2 = options.width / 18;
          if (options.accuracy)
            counter2 = options.accuracy;
          var columnText;
          var latestTextNode = null;
          while ($parentColumn.height() < height && oText.length) {
            if (oText.indexOf(' ', counter2) != '-1') {
              columnText = oText.substring(0, oText.indexOf(' ', counter2));
            } else {
              columnText = oText;
            }
            latestTextNode = document.createTextNode(columnText);
            $putInHere.append(latestTextNode);

            if (oText.length > counter2) {
              oText = oText.substring(oText.indexOf(' ', counter2));
            } else {
              oText = "";
            }
          }
          if ($parentColumn.height() >= height && latestTextNode != null) {
            // too tall :(
            $putInHere[0].removeChild(latestTextNode);
            oText = latestTextNode.nodeValue + oText;
          }
          if (oText.length) {
            $item[0].nodeValue = oText;
          } else {
            return false; // we ate the whole text node, move on to the next node
          }
        }

        if ($pullOutHere.children().length) {
          $pullOutHere.prepend($item);
        } else {
          $pullOutHere.append($item);
        }

        return $item[0].nodeType == 3;
      }

      function split($putInHere, $pullOutHere, $parentColumn, height) {
        if ($pullOutHere.children().length) {
          $cloneMe = $pullOutHere.children(":first");
          $clone = $cloneMe.clone(true);
          if ($clone.attr("nodeType") == 1 && !$clone.hasClass("dontend")) {
            $putInHere.append($clone);
            if ($clone.is("img") && $parentColumn.height() < height + 20) {
              $cloneMe.remove();
            } else if (!$cloneMe.hasClass("dontsplit") && $parentColumn.height() < height + 20) {
              $cloneMe.remove();
            } else if ($clone.is("img") || $cloneMe.hasClass("dontsplit")) {
              $clone.remove();
            } else {
              $clone.empty();
              if (!columnize($clone, $cloneMe, $parentColumn, height)) {
                if ($cloneMe.children().length) {
                  split($clone, $cloneMe, $parentColumn, height);
                }
              }
              if ($clone.get(0).childNodes.length == 0) {
                // it was split, but nothing is in it :(
                $clone.remove();
              }
            }
          }
        }
      }


      function singleColumnizeIt() {
        if ($inBox.data("columnized") && $inBox.children().length == 1) {
          return;
        }
        $inBox.data("columnized", true);
        $inBox.data("columnizing", true);

        $inBox.empty();
        $inBox.append($("<div class='first last column' style='width:98%; padding: 3px; float: " + options.float + ";'></div>")); //"
        $col = $inBox.children().eq($inBox.children().length - 1);
        $destroyable = $cache.clone(true);
        if (options.overflow) {
          targetHeight = options.overflow.height;
          columnize($col, $destroyable, $col, targetHeight);
          // make sure that the last item in the column isn't a "dontend"
          if (!$destroyable.children().find(":first-child").hasClass("dontend")) {
            split($col, $destroyable, $col, targetHeight);
          }

          while (checkDontEndColumn($col.children(":last").length && $col.children(":last").get(0))) {
            var $lastKid = $col.children(":last");
            $lastKid.remove();
            $destroyable.prepend($lastKid);
          }

          var html = "";
          var div = document.createElement('DIV');
          while ($destroyable[0].childNodes.length > 0) {
            var kid = $destroyable[0].childNodes[0];
            for (var i = 0; i < kid.attributes.length; i++) {
              if (kid.attributes[i].nodeName.indexOf("jQuery") == 0) {
                kid.removeAttribute(kid.attributes[i].nodeName);
              }
            }
            div.innerHTML = "";
            div.appendChild($destroyable[0].childNodes[0]);
            html += div.innerHTML;
          }
          var overflow = $(options.overflow.id)[0];
          overflow.innerHTML = html;

        } else {
          $col.append($destroyable);
        }
        $inBox.data("columnizing", false);

        if (options.overflow) {
          options.overflow.doneFunc();
        }

      }

      function checkDontEndColumn(dom) {
        if (dom.nodeType != 1) return false;
        if ($(dom).hasClass("dontend")) return true;
        if (dom.childNodes.length == 0) return false;
        return checkDontEndColumn(dom.childNodes[dom.childNodes.length - 1]);
      }

      function columnizeIt() {
        if (lastWidth == $inBox.width()) return;
        lastWidth = $inBox.width();

        var numCols = Math.round($inBox.width() / options.width);
        if (options.columns) numCols = options.columns;
        //			if ($inBox.data("columnized") && numCols == $inBox.children().length) {
        //				return;
        //			}
        if (numCols <= 1) {
          return singleColumnizeIt();
        }
        if ($inBox.data("columnizing")) return;
        $inBox.data("columnized", true);
        $inBox.data("columnizing", true);

        $inBox.empty();
        $inBox.append($("<div style='width:" + (Math.round(100 / numCols) - 2) + "%; padding: 3px; float: " + options.float + ";'></div>")); //"
        $col = $inBox.children(":last");
        $col.append($cache.clone());
        maxHeight = $col.height();
        $inBox.empty();

        var targetHeight = maxHeight / numCols;
        var firstTime = true;
        var maxLoops = 3;
        var scrollHorizontally = false;
        if (options.overflow) {
          maxLoops = 1;
          targetHeight = options.overflow.height;
        } else if (options.height && options.width) {
          maxLoops = 1;
          targetHeight = options.height;
          scrollHorizontally = true;
        }

        for (var loopCount = 0; loopCount < maxLoops; loopCount++) {
          $inBox.empty();
          var $destroyable;
          try {
            $destroyable = $cache.clone(true);
          } catch (e) {
            // jquery in ie6 can't clone with true
            $destroyable = $cache.clone();
          }
          $destroyable.css("visibility", "hidden");
          // create the columns
          for (var i = 0; i < numCols; i++) {
            /* create column */
            var className = (i == 0) ? "first column" : "column";
            var className = (i == numCols - 1) ? ("last " + className) : className;
            $inBox.append($("<div class='" + className + "' style='width:" + (Math.round(100 / numCols) - 2) + "%; float: " + options.float + ";'></div>")); //"
          }

          // fill all but the last column (unless overflowing)
          var i = 0;
          while (i < numCols - (options.overflow ? 0 : 1) || scrollHorizontally && $destroyable.children().length) {
            if ($inBox.children().length <= i) {
              // we ran out of columns, make another
              $inBox.append($("<div class='" + className + "' style='width:" + (Math.round(100 / numCols) - 2) + "%; float: " + options.float + ";'></div>")); //"
            }
            var $col = $inBox.children().eq(i);
            columnize($col, $destroyable, $col, targetHeight);
            // make sure that the last item in the column isn't a "dontend"
            if (!$destroyable.children().find(":first-child").hasClass("dontend")) {
              split($col, $destroyable, $col, targetHeight);
            } else {
              //						alert("not splitting a dontend");
            }

            while (checkDontEndColumn($col.children(":last").length && $col.children(":last").get(0))) {
              var $lastKid = $col.children(":last");
              $lastKid.remove();
              $destroyable.prepend($lastKid);
            }
            i++;
          }
          if (options.overflow && !scrollHorizontally) {
            var IE6 = false/*@cc_on || @_jscript_version < 5.7@*/;
            var IE7 = (document.all) && (navigator.appVersion.indexOf("MSIE 7.") != -1);
            if (IE6 || IE7) {
              var html = "";
              var div = document.createElement('DIV');
              while ($destroyable[0].childNodes.length > 0) {
                var kid = $destroyable[0].childNodes[0];
                for (var i = 0; i < kid.attributes.length; i++) {
                  if (kid.attributes[i].nodeName.indexOf("jQuery") == 0) {
                    kid.removeAttribute(kid.attributes[i].nodeName);
                  }
                }
                div.innerHTML = "";
                div.appendChild($destroyable[0].childNodes[0]);
                html += div.innerHTML;
              }
              var overflow = $(options.overflow.id)[0];
              overflow.innerHTML = html;
            } else {
              $(options.overflow.id).empty().append($destroyable.children().clone(true));
            }
          } else if (!scrollHorizontally) {
            // the last column in the series
            $col = $inBox.children().eq($inBox.children().length - 1);
            while ($destroyable.children().length) $col.append($destroyable.children(":first"));
            var afterH = $col.height();
            var diff = afterH - targetHeight;
            var totalH = 0;
            var min = 10000000;
            var max = 0;
            var lastIsMax = false;
            $inBox.children().each(function($inBox) {
              return function($item) {
                var h = $inBox.children().eq($item).height();
                lastIsMax = false;
                totalH += h;
                if (h > max) {
                  max = h;
                  lastIsMax = true;
                }
                if (h < min) min = h;
              } 
            } ($inBox));

            var avgH = totalH / numCols;
            if (options.lastNeverTallest && lastIsMax) {
              // the last column is the tallest
              // so allow columns to be taller
              // and retry
              targetHeight = targetHeight + 30;
              if (loopCount == maxLoops - 1) maxLoops++;
            } else if (max - min > 30) {
              // too much variation, try again
              targetHeight = avgH + 30;
            } else if (Math.abs(avgH - targetHeight) > 20) {
              // too much variation, try again
              targetHeight = avgH;
            } else {
              // solid, we're done
              loopCount = maxLoops;
            }
          } else {
            // it's scrolling horizontally, fix the width/classes of the columns
            $inBox.children().each(function(i) {
              $col = $inBox.children().eq(i);
              $col.width(options.width + "px");
              if (i == 0) {
                $col.addClass("first");
              } else if (i == $inBox.children().length - 1) {
                $col.addClass("last");
              } else {
                $col.removeClass("first");
                $col.removeClass("last");
              }
            });
            $inBox.width($inBox.children().length * options.width + "px");
          }
          $inBox.append($("<br style='clear:both;'>"));
        }
        $inBox.find('.column').find(':first.removeiffirst').remove();
        $inBox.find('.column').find(':last.removeiflast').remove();
        $inBox.data("columnizing", false);

        if (options.overflow) {
          options.overflow.doneFunc();
        }
        options.doneFunc();
      }
    });
  };
})(jQuery);


