/**
* Condense 0.1 - Condense and expand text heavy elements
*
* (c) 2008 Joseph Sillitoe
* Dual licensed under the MIT License (MIT-LICENSE) and GPL License,version 2 (GPL-LICENSE).
*/
/*
* jQuery plugin
*
* usage:
*
* $(document).ready(function(){
* $('#example1').condense();
* });
*
* Options:
* condensedLength: Target length of condensed element. Default: 200
* minTrail: Minimun length of the trailing text. Default: 20
* delim: Delimiter used for finding the break point. Default: " " - {space}
* moreText: Text used for the more control. Default: [more]
* lessText: Text used for the less control, or null/empty for no less control. Default: [less]
* ellipsis: Text added to condensed element. Default: ( ... )
* animate: True iff expanding and condensing should be animated. Default: true
* moreSpeed: Animation Speed for expanding. Default: "normal"
* lessSpeed: Animation Speed for condensing. Default: "normal"
* easing: Easing algorith. Default: "linear"
* expandedWidth: Width of the expanded text (optional)
*/
(function($) {
// plugin definition
$.fn.condense = function(options) {
var opts = $.extend({}, $.fn.condense.defaults, options); // build main options before element iteration
$.metadata ? debug('metadata plugin detected', opts) : debug('metadata plugin not present', opts);//detect the metadata plugin?
// iterate each matched element
return this.each(function() {
var $this = $(this);
// support metadata plugin (v2.0)
var o = $.metadata ? $.extend({}, opts, $this.metadata()) : opts; // build element specific options
debug('Condensing ['+$this.text().length+']: '+$this.text(), opts);
$this.wrap('');
var $par = $this.parent();
var clone = cloneCondensed($this,o);
if (clone){
clone.addClass(o.condensedClass);
$this.addClass(o.expandedClass);
// id attribute switch. make sure that the visible elem keeps the original id (if set).
$this.attr('id') ? $this.attr('id','condensed_'+$this.attr('id')) : false;
var controlMore = " "+o.moreText+"";
if (o.inline) {
clone.append(controlMore);
} else {
clone.append(o.ellipsis + controlMore);
}
$par.append(clone);
if(o.lessText) {
var controlLess = " "+o.lessText+"";
$this.append(controlLess).hide();
}
$('.condense_control_more',clone).click(function(){
debug('moreControl clicked.', opts);
$par.trigger(o.moreEvent);
});
$('.condense_control_less',$this).click(function(){
debug('lessControl clicked.', opts);
$par.trigger(o.lessEvent);
});
var isExpanded = false;
$par.bind(o.lessEvent, function() {
if(isExpanded) {
triggerCondense($par,o)
isExpanded = false;
}
});
$par.bind(o.moreEvent, function() {
if(! isExpanded) {
triggerExpand($par,o)
isExpanded = true;
}
});
}
});
};
function cloneCondensed(elem, opts){
// Try to clone and condense the element. if not possible because of the length/minTrail options, return false.
// also, dont count tag declarations as part of the text length.
// check the length of the text first, return false if too short.
if ($.trim(elem.text()).length <= opts.condensedLength + opts.minTrail){
debug('element too short: skipping.', opts);
return false;
}
var fullbody = $.trim(elem.html());
var fulltext = $.trim(elem.text());
var delim = opts.delim;
var clone = elem.clone();
var delta = 0;
do {
// find the location of the next potential break-point.
var loc = findDelimiterLocation(fullbody, opts.delim, (opts.condensedLength + delta));
//set the html of the clone to the substring html of the original
if (opts.inline) {
clone.html(fullbody.substring(0,(loc+1)) + ' ' + opts.ellipsis);
} else {
clone.html(fullbody.substring(0, (loc + 1)));
}
var cloneTextLength = clone.text().length;
var cloneHtmlLength = clone.html().length;
delta = clone.html().length - cloneTextLength;
debug ("condensing... [html-length:"+cloneHtmlLength+" text-length:"+cloneTextLength+" delta: "+delta+" break-point: "+loc+"]");
//is the length of the clone text long enough?
}while(delta && clone.text().length < opts.condensedLength );
// after skipping ahead to the delimiter, do we still have enough trailing text?
if ((fulltext.length - cloneTextLength) < opts.minTrail){
debug('not enough trailing text: skipping.', opts);
return false;
}
debug('clone condensed. [text-length:'+cloneTextLength+']', opts);
return clone;
}
function findDelimiterLocation(html, delim, startpos){
// find the location inside the html of the delimiter, starting at the specified length.
var foundDelim = false;
var loc = startpos;
do {
var loc = html.indexOf(delim, loc);
if (loc < 0){
debug ("No delimiter found.");
return html.length;
} // if there is no delimiter found, just return the length of the entire html string.
foundDelim = true;
while (isInsideTag(html, loc)) {
// if we are inside a tag, this delim doesn't count. keep looking...
loc++;
foundDelim = false;
}
}while(!foundDelim);
debug ("Delimiter found in html at: "+loc);
return loc;
}
function isInsideTag(html, loc){
var startTagIndex = html.indexOf('<', loc);
var endTagIndex = html.indexOf('>', loc);
return (startTagIndex === -1 && endTagIndex >0) || (endTagIndex < startTagIndex);
}
function getElements(par){
var orig = par.children().first(); // The original element will be the first element in the parent
var condensed = orig.next(); // The condensed element will be the original immediate next sibling.
return {orig: orig, condensed: condensed};
}
function triggerCondense(par, opts){
var elements = getElements(par);
elements.condensed.show();
var con_w = elements.condensed.width();
var con_h = elements.condensed.height();
elements.condensed.hide(); //briefly flashed the condensed element so we can get the target width/height
var orig_w = elements.orig.width();
var orig_h = elements.orig.height();
if (opts.animate) {
elements.orig.animate({height: con_h, width: con_w, opacity: 1}, opts.lessSpeed, opts.easing,
function () {
elements.orig.height(orig_h).width(orig_w).hide();
elements.condensed.show();
});
}else{
elements.orig.height(orig_h).width(orig_w).hide();
elements.condensed.show();
}
}
function triggerExpand(par, opts){
var elements = getElements(par);
elements.orig.show();
var orig_w = elements.orig.width();
var orig_h = elements.orig.height();
elements.orig.width(elements.condensed.width()+"px").height(elements.condensed.height()+"px");
elements.condensed.hide();
if (opts.animate) {
elements.orig.animate({height: orig_h, width: orig_w, opacity: 1}, opts.moreSpeed, opts.easing);
}else{
elements.orig.height(orig_h).width(orig_w).show();
}
if(elements.condensed.attr('id')){
var idAttr = elements.condensed.attr('id');
elements.condensed.attr('id','condensed_'+idAttr);
elements.orig.attr('id',idAttr);
}
}
/**
* private function for debugging
*/
function debug(str, opts) {
if (opts && opts.debug && window.console && window.console.log){window.console.log(str);}
}
// plugin defaults
$.fn.condense.defaults = {
condensedLength: 200,
minTrail: 20,
delim: " ",
moreText: "[more]",
lessText: "[less]",
ellipsis: " ( ... )",
inline: true,
animate: true,
moreSpeed: "normal",
lessSpeed: "normal",
easing: "linear",
moreEvent: 'expand.condensePlugin',
lessEvent: 'condense.condensePlugin',
condensedClass: '',
expandedClass: '',
eventProxy: undefined,
debug: true
};
})(jQuery);