<commit_before>
// fetch a URL
function httpGet(theUrl) {
    var xmlHttp = null;
    xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", theUrl, false );
    xmlHttp.send( null );
    return xmlHttp.responseText;
}

// get a cookie
function getCookies(domain, name, callback) {
    chrome.cookies.get({"url": domain, "name": name}, function(cookie) {
        if(callback) {
            callback(cookie.value);
        }
    });
}

// refresh the pop3 account
function popPull() {
// in normal gmail use "_reqid" and "rid" change every time. Google doesn't complain if we don't change them.
// "at" is taken from the GMAIL_AT session cookie (the Session Authorization Key"). If this value is wrong Google
// returns an internal server error
// "act" specifies which account to pull "act=cma_2" and "act=cma_1" are my two accounts
    getCookies("https://mail.google.com/mail/u/0", "GMAIL_AT", function(id) {
        var sessionAuthorizationKey = id;
        var url = "https://mail.google.com/mail/u/0/?ui=2&ik=246937ba7b&rid=b08b..&at=" + sessionAuthorizationKey + "&view=up&act=cma_2&_reqid=551123&pcd=1&mb=0&rt=c";
        httpGet(url);
    });
}

// pull every 60 seconds
setInterval(function() {popPull()}, 60000);

// pull any time the refresh button is pressed
chrome.pageAction.onClicked.addListener(function(tab) {
    popPull();
});

// enable this pageAction on "mail.google.com" only
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    if (tab.url.indexOf('https://mail.google.com') == 0) {
        chrome.pageAction.show(tabId);
    }
});
<commit_msg>updated to refresh all pop3 accounts once a minute<commit_after>
// fetch a URL
function httpGet(url) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", url, false );
    xmlHttp.send( null );
    return xmlHttp.responseText;
}

// get a cookie
function getCookie(domain, name, callback) {
    chrome.cookies.get({"url": domain, "name": name}, function(cookie) {
        callback(cookie.value);
    });
}

// get the session auth key cookie (GMAIL_AT)
function getSessionAuthKeyCookie(callback) {
    getCookie("https://mail.google.com/mail/u/0", "GMAIL_AT", function(sessionAuthKey) {
        callback(sessionAuthKey);
    });
}

// the url to hit to refresh a given account
function getPop3PullUrl(accountIdx, callback) {
    // in normal gmail use parameters like "_reqid" and "rid" change each time but google doesn't seem to complain if they don't
    // the "at" parameter must match the "session auth key cookie" GMAIL_AT (or it causes an google internal server error)
    // the "act" specifies the id to the account to pull from "act=cma_2" and "act=cma_1" are my two
    getSessionAuthKeyCookie(function(sessionAuthKey) {
        var url = "https://mail.google.com/mail/u/0/?ui=2&ik=246937ba7b&rid=b08b..&at=" + sessionAuthKey + "&view=up&act=cma_" + accountIdx + "&_reqid=551123&pcd=1&mb=0&rt=c";
        callback(url);
    });
}

// the url to find out what pop3 accounts the user has
function getAccountsUrl(callback) {
    getSessionAuthKeyCookie(function(sessionAuthKey) {
        var url = "https://mail.google.com/mail/u/0/?ui=2&ik=246937ba7b&rid=eefa..&at=" + sessionAuthKey + "&view=up&act=rap&_reqid=1366360&pcd=1&mb=0&rt=j";
        callback(url);
    });
}

// fetch the pop3 accounts
function getAccounts(callback) {

    getAccountsUrl(function(url) {

        // get the account list data
        var str = httpGet(url);

        // get just the "ama" section (assumes following section is "a")
        // useful regex tool: https://regex101.com
        str = str.match(/.*,\["ama",(.|\n)*,\["a",/g);

        // parse out each account (index and label)
        var accountArray = [];
        var regex = /.*?\[(\d),\"(.*?@.*?)\"/g;
        var match;
        while ((match = regex.exec(str)) != null) {
            var index = match[1];
            var label = match[2];
            var account = {"index":index, "label":label};
            accountArray.push(account);
        }

        callback(accountArray);
    });
}

// print pop3 accounts to screen
function dumpAccounts() {
    getAccounts(function(accountArray) {
        var arrayLength = accountArray.length;
        for (var i = 0; i < arrayLength; i++) {
            var account = accountArray[i];
            console.log("account.index=" + account["index"] + " account.label=" + account["label"]);
        }
    });
}

// pull a pop3 account with this index
function popPullAccount(accountIdx) {
    getPop3PullUrl(accountIdx, function(url) {
        httpGet(url);
    });
}

// refresh the pop3 account
function popPullAllAccounts() {
     getAccounts(function(accountArray) {
        var arrayLength = accountArray.length;
        for (var i = 0; i < arrayLength; i++) {
            var account = accountArray[i];
            popPullAccount(account["index"]);
        }
    });
}

function popPull() {
    popPullAllAccounts();
}

// pull every 60 seconds
setInterval(function() {
    popPull()
}, 60000);

// pull any time the refresh button is pressed
chrome.pageAction.onClicked.addListener(function(tab) {
    popPull();
});

// enable this pageAction on "mail.google.com" only
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    if (tab.url.indexOf('https://mail.google.com') == 0) {
        chrome.pageAction.show(tabId);
        popPull();
    }
});

<|endoftext|>


<commit_before>import Vue from 'vue'
import Mint from 'mint-ui'
import Resource from 'vue-resource'
import Router from 'vue-router'
import routerMap from './router'
import App from './App.vue'

Vue.use(Mint);
Vue.use(Router);
Vue.use(Resource);

Vue.config.devtools = true;
Vue.http.options.emulateJSON = true;
Vue.http.options.emulateHTTP = true;

// laravel csrf token
Vue.http.headers.common['X-CSRF-TOKEN'] = document.querySelector('#csrf-token').getAttribute('content');
const router = new Router({
    history: false,
    mode: 'html5'
});

routerMap(router);

router.start(App, 'body');
<commit_msg>babel-polyfill<commit_after>import Vue from 'vue'
import Mint from 'mint-ui'
import Resource from 'vue-resource'
import Router from 'vue-router'
import routerMap from './router'
import App from './App.vue'

Vue.use(Mint);
Vue.use(Router);
Vue.use(Resource);

Vue.config.devtools = true;
Vue.http.options.emulateJSON = true;
Vue.http.options.emulateHTTP = true;
Vue.http.interceptors.push({
    response(response) {
        // 判断是否在微信内置浏览器中
        let ua = window.navigator.userAgent.toLowerCase();
        if(ua.match(/MicroMessenger/i) == 'micromessenger') {
            // 若response.data为String类型，将其格式化成JSON类型
            if (typeof response.data === "string") {
                response.data = JSON.parse(response.data)
            }
        }
    },
});

// laravel csrf token
Vue.http.headers.common['X-CSRF-TOKEN'] = document.querySelector('#csrf-token').getAttribute('content');
const router = new Router({
    history: false,
    mode: 'html5'
});

routerMap(router);

router.start(App, 'body');
<|endoftext|>


<commit_before>/*
functional - helpers for the functional style in javascript

functional.map(func, arr)
  returns an array of objects resulting from calling
  func(arr[index], index, arr) for every assigned index in arr

functional.filter(func, arr)
  returns an array of all the elements in arr for which
  func(arr[index], index, arr) returns a true-ish value

functional.foreach(func, arr)
  runs func(arr[index], index, arr) for every index in arr

functional.all(func, arr)
  runs func(arr[index], index, arr) for every index in arr until it returns
  a false-ish value, at which point it returns false. if it gets all the
  way through, returns true

functional.any(func, arr)
  runs func(arr[index], index, arr) for every index in arr until it returns
  a true-ish value, at which point it returns true. if it gets all the
  way through, returns false

functional.partial(func, ...early_args)
  returns a function that takes any number of args and sticks them onto the
  end of the early_args and uses them as arguments to the original function

functional.revpartial(func, ...early_args)
  does the same as partial, but reverses the order of the early args and
  places them after the late args

functional.foldright(func, arr)
  applies the func function to the first two items in arr, then on that
  result and the third, then on that result and the fourth, etc, returning
  the final result

functional.foldleft(func, arr)
  foldright on the reversed arr

functional.compose(...functions)
  returns a function that applies each of the argument functions in order.
  only the first function may take more than one argument, as each of the
  others will accept the returned value of the previous one

functional.generate(item)
  returns a function that will always return item

functional.propgetter(obj, name)
  returns obj[name] - primarily useful for creating other functions with
  functional.partial and functional.revpartial

functional.propsetter(obj, name, value)
  runs 'obj[name] = value' - also mainly for use with functional.partial
  and functional.revpartial

functional.methodrunner(obj, name, ...args)
  runs obj[name](...args)

function paramtransform(func, transformer)
  returns a function that will run func on the result of running
  transformer on the arguments. transformer can take any number of
  arguments but return an arr (these will be the arguments passed to
  func)
*/

/*global com*/

(function(tjp) {
  tjp.functional = {};

  function isfunc(f) {
    if (typeof f !== "function") throw new TypeError();
  };

  tjp.functional.map = function(func, arr) {
    if (Array.prototype.map) return arr.map(func)

    var results, i, len;
    isfunc(func);
    len = arr.length;
    results = new Array(len);
    for (i = 0; i < len; i++) if (i in arr) results[i] = func(arr[i], i, arr);
    return results;
  };

  tjp.functional.filter = function(func, arr) {
    if (Array.prototype.filter) return arr.filter(func);

    var results, i, val;
    isfunc(func);
    results = new Array();
    for (i = 0; i < arr.length; i++) if (i in arr) {
      val = arr[i];
      if (func(val, i, arr)) results.push(val);
    }
    return results;
  };

  tjp.functional.foreach = function(func, arr) {
    if (Array.prototype.forEach) return arr.forEach(func);

    isfunc(func);
    for (var i = 0; i < arr.length; i++) if (i in arr) func(arr[i], i, arr);
  };

  tjp.functional.all = function(func, arr) {
    if (Array.prototype.every) return arr.every(func);

    isfunc(func);
    for (var i = 0; i < arr.length; i++) {
      if (i in arr && !func(arr[i], i, arr)) return false;
    }
    return true;
  };

  tjp.functional.any = function(func, arr) {
    if (Array.prototype.some) return arr.some(func);

    isfunc(func);
    for (var i = 0; i < arr.length; i++) {
      if (i in arr && func(arr[i], i, arr)) return true;
    }
    return false;
  };

  tjp.functional.partial = function(func) {
    var args = arguments;
    return function() {
      var i;
      for (i = 1; i < arguments.length; i++) args.push(arguments[i]);
      return func.apply(null, args);
    };
  };

  tjp.functional.revpartial = function(func) {
    var outer = arguments;
    return function() {
      var i, args = arguments;
      for (i = outer.length - 1; i > 0; i--) args.push(outer[i]);
      return func.apply(null, args);
    };
  };

  tjp.functional.foldleft = function(func, arr, initial) {
    if (Array.prototype.reduce) return arr.reduce(func, initial);

    isfunc(func);
    var i, val, len = arr.length;
    if (len === 0 && initial === undefined) throw new TypeError();

    if (initial !== undefined) val = initial;
    else do {
      if (i in arr) {
        val = arr[i++];
        break;
      }

      if (++i >= len) throw new TypeError();
    } while (true);

    for (; i < len; i++) {
      if (i in arr) val = func.call(null, val, arr[i], i, arr);
    }

    return val;
  };

  tjp.functional.foldright = function(func, arr, initial) {
    if (Array.prototype.reduceRight) return arr.reduceRight(func, initial);

    return tjp.functional.foldleft(func, arr.slice().reverse(), initial);
  };

  tjp.functional.compose = function() {
    var outerargs = arguments;
    return function(item) {
      var i;
      for (i = 0; i < outerargs.length; i++) item = outerargs[i](item);
      return item;
    };
  };

  tjp.functional.generate = function(item) {
    return function() { return item; };
  };

  tjp.functional.propgetter = function(obj, name) {
    return obj[name];
  };

  tjp.functional.propsetter = function(obj, name, value) {
    obj[name] = value;
  };

  tjp.functional.methodrunner = function(obj, name) {
    var args = [], i;
    for (i = 2; i < arguments.length; i++) args.push(arguments[i]);
    return obj[name].apply(obj, args);
  };

  tjp.functional.paramtransform = function(func, transformer) {
    return function() {
      var args = transformer.apply(null, arguments);
      return func.apply(null, args);
    };
  };
})(com.travisjparker);
<commit_msg>some bugfixes to functional.js<commit_after>/*
functional - helpers for the functional style in javascript

functional.map(func, arr)
  returns an array of objects resulting from calling
  func(arr[index], index, arr) for every assigned index in arr

functional.filter(func, arr)
  returns an array of all the elements in arr for which
  func(arr[index], index, arr) returns a true-ish value

functional.foreach(func, arr)
  runs func(arr[index], index, arr) for every index in arr

functional.all(func, arr)
  runs func(arr[index], index, arr) for every index in arr until it returns
  a false-ish value, at which point it returns false. if it gets all the
  way through, returns true

functional.any(func, arr)
  runs func(arr[index], index, arr) for every index in arr until it returns
  a true-ish value, at which point it returns true. if it gets all the
  way through, returns false

functional.partial(func, ...early_args)
  returns a function that takes any number of args and sticks them onto the
  end of the early_args and uses them as arguments to the original function

functional.revpartial(func, ...early_args)
  does the same as partial, but reverses the order of the early args and
  places them after the late args

functional.foldright(func, arr)
  applies the func function to the first two items in arr, then on that
  result and the third, then on that result and the fourth, etc, returning
  the final result

functional.foldleft(func, arr)
  foldright on the reversed arr

functional.compose(...functions)
  returns a function that applies each of the argument functions in order.
  only the first function may take more than one argument, as each of the
  others will accept the returned value of the previous one

functional.generate(item)
  returns a function that will always return item

functional.propgetter(obj, name)
  returns obj[name] - primarily useful for creating other functions with
  functional.partial and functional.revpartial

functional.propsetter(obj, name, value)
  runs 'obj[name] = value' - also mainly for use with functional.partial
  and functional.revpartial

functional.methodrunner(obj, name, ...args)
  runs obj[name](...args)

function paramtransform(func, transformer)
  returns a function that will run func on the result of running
  transformer on the arguments. transformer can take any number of
  arguments but return an arr (these will be the arguments passed to
  func)
*/

/*global com*/

(function(tjp) {
  tjp.functional = {};

  function isfunc(f) {
    if (typeof f !== "function") throw new TypeError();
  };

  tjp.functional.map = function(func, arr) {
    if (Array.prototype.map) return arr.map(func);

    var results, i, len;
    isfunc(func);
    len = arr.length;
    results = new Array(len);
    for (i = 0; i < len; i++) if (i in arr) results[i] = func(arr[i], i, arr);
    return results;
  };

  tjp.functional.filter = function(func, arr) {
    if (Array.prototype.filter) return arr.filter(func);

    var results, i, val;
    isfunc(func);
    results = new Array();
    for (i = 0; i < arr.length; i++) if (i in arr) {
      val = arr[i];
      if (func(val, i, arr)) results.push(val);
    }
    return results;
  };

  tjp.functional.foreach = function(func, arr) {
    if (Array.prototype.forEach) return arr.forEach(func);

    isfunc(func);
    for (var i = 0; i < arr.length; i++) if (i in arr) func(arr[i], i, arr);
  };

  tjp.functional.all = function(func, arr) {
    if (Array.prototype.every) return arr.every(func);

    isfunc(func);
    for (var i = 0; i < arr.length; i++) {
      if (i in arr && !func(arr[i], i, arr)) return false;
    }
    return true;
  };

  tjp.functional.any = function(func, arr) {
    if (Array.prototype.some) return arr.some(func);

    isfunc(func);
    for (var i = 0; i < arr.length; i++) {
      if (i in arr && func(arr[i], i, arr)) return true;
    }
    return false;
  };

  tjp.functional.partial = function(func) {
    var args = arguments;
    return function() {
      for (var i = 0; i < arguments.length; i++)
        Array.prototype.push.call(args, arguments[i]);
      return func.apply(null, Array.prototype.slice.call(args, 1));
    };
  };

  tjp.functional.revpartial = function(func) {
    var outer = arguments;
    return function() {
      var i, args = arguments;
      for (i = outer.length - 1; i > 0; i--)
        Array.prototype.push.call(args, outer[i]);
      return func.apply(null, args);
    };
  };

  tjp.functional.foldleft = function(func, arr, initial) {
    if (Array.prototype.reduce) return arr.reduce(func, initial);

    isfunc(func);
    var i, val, len = arr.length;
    if (len === 0 && initial === undefined) throw new TypeError();

    if (initial !== undefined) val = initial;
    else do {
      if (i in arr) {
        val = arr[i++];
        break;
      }

      if (++i >= len) throw new TypeError();
    } while (true);

    for (; i < len; i++) {
      if (i in arr) val = func.call(null, val, arr[i], i, arr);
    }

    return val;
  };

  tjp.functional.foldright = function(func, arr, initial) {
    if (Array.prototype.reduceRight) return arr.reduceRight(func, initial);

    return tjp.functional.foldleft(func, arr.slice().reverse(), initial);
  };

  tjp.functional.compose = function() {
    var outerargs = arguments;
    return function(item) {
      var i;
      for (i = 0; i < outerargs.length; i++) item = outerargs[i](item);
      return item;
    };
  };

  tjp.functional.generate = function(item) {
    return function() { return item; };
  };

  tjp.functional.propgetter = function(obj, name) {
    return obj[name];
  };

  tjp.functional.propsetter = function(obj, name, value) {
    obj[name] = value;
  };

  tjp.functional.methodrunner = function(obj, name) {
    var args = [], i;
    for (i = 2; i < arguments.length; i++) args.push(arguments[i]);
    return obj[name].apply(obj, args);
  };

  tjp.functional.paramtransform = function(func, transformer) {
    return function() {
      var args = transformer.apply(null, arguments);
      return func.apply(null, args);
    };
  };
})(com.travisjparker);
<|endoftext|>


<commit_before>/**
* 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. Default: [less]
*  ellipsis: Text added to condensed element. Default:  ( ... )
*  moreSpeed: Animation Speed for expanding. Default: "normal"
*  lessSpeed: Animation Speed for condensing. Default: "normal"
*  easing: Easing algorith. Default: "linear"
*/

(function($) {

  // plugin definition
  $.fn.condense = function(options) {

    $.metadata ? debug('metadata plugin detected') : debug('metadata plugin not present');//detect the metadata plugin?

    var opts = $.extend({}, $.fn.condense.defaults, options); // build main options before element iteration

    // iterate each matched element
    return this.each(function() {
	    $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());

      var clone = cloneCondensed($this,o);

      if (clone){
        // 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+"";
        var controlLess = " "+o.lessText+"";
        clone.append(o.ellipsis + controlMore);
        $this.after(clone).hide().append(controlLess);

        $('.condense_control_more',clone).click(function(){
          debug('moreControl clicked.');
          triggerExpand($(this),o)
        });

        $('.condense_control_less',$this).click(function(){
          debug('lessControl clicked.');
          triggerCondense($(this),o)
        });
      }

	  });
  };

  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.
       $(document).ready(function(){
          //example 1: using default options
          $('#example1').condense();
    // 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.');
      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
      clone.html($.trim(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(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.');
      return false;
    }

    debug('clone condensed. [text-length:'+cloneTextLength+']');
    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){
    return (html.indexOf('>',loc) < html.indexOf('<',loc));
  }

  function triggerCondense(control, opts){
    debug('Condense Trigger: '+control.html());
    var orig = control.parent(); // The original element will be the control's immediate parent.
    var condensed = orig.next(); // The condensed element will be the original immediate next sibling.
    condensed.show();
    var con_w  = condensed.width();
    var con_h = condensed.height();
    condensed.hide(); //briefly flashed the condensed element so we can get the target width/height
    var orig_w  = orig.width();
    var orig_h = orig.height();
    orig.animate({height:con_h, width:con_w, opacity: 1}, opts.lessSpeed, opts.easing,
      function(){
        orig.height(orig_h).width(orig_w).hide();
        condensed.show();
      });
  }

  function triggerExpand(control, opts){
    debug('Expand Trigger: '+control.html());
    var condensed = control.parent(); // The condensed element will be the control's immediate parent.
    var orig = condensed.prev(); // The original element will be the condensed immediate previous sibling.
    orig.show();
    var orig_w  = orig.width();
    var orig_h = orig.height();
    orig.width(condensed.width()+"px").height(condensed.height()+"px");
    condensed.hide();
    orig.animate({height:orig_h, width:orig_w, opacity: 1}, opts.moreSpeed, opts.easing);
    if(condensed.attr('id')){
      var idAttr = condensed.attr('id');
      condensed.attr('id','condensed_'+idAttr);
      orig.attr('id',idAttr);
    }
  }

  /**
   * private function for debugging
   */
  function debug($obj) {if (window.console && window.console.log){window.console.log($obj);}};

  // plugin defaults
  $.fn.condense.defaults = {
    condensedLength: 200,
    minTrail: 20,
    delim: " ",
    moreText: "[more]",
    lessText: "[less]",
    ellipsis: " ( ... )",
    moreSpeed: "normal",
    lessSpeed: "normal",
    easing: "linear"
  };

})(jQuery);
<commit_msg>fixed typo bug.<commit_after>/**
* 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. Default: [less]
*  ellipsis: Text added to condensed element. Default:  ( ... )
*  moreSpeed: Animation Speed for expanding. Default: "normal"
*  lessSpeed: Animation Speed for condensing. Default: "normal"
*  easing: Easing algorith. Default: "linear"
*/

(function($) {

  // plugin definition
  $.fn.condense = function(options) {

    $.metadata ? debug('metadata plugin detected') : debug('metadata plugin not present');//detect the metadata plugin?

    var opts = $.extend({}, $.fn.condense.defaults, options); // build main options before element iteration

    // iterate each matched element
    return this.each(function() {
	    $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());

      var clone = cloneCondensed($this,o);

      if (clone){
        // 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+"";
        var controlLess = " "+o.lessText+"";
        clone.append(o.ellipsis + controlMore);
        $this.after(clone).hide().append(controlLess);

        $('.condense_control_more',clone).click(function(){
          debug('moreControl clicked.');
          triggerExpand($(this),o)
        });

        $('.condense_control_less',$this).click(function(){
          debug('lessControl clicked.');
          triggerCondense($(this),o)
        });
      }

	  });
  };

  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.');
      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
      clone.html($.trim(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(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.');
      return false;
    }

    debug('clone condensed. [text-length:'+cloneTextLength+']');
    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){
    return (html.indexOf('>',loc) < html.indexOf('<',loc));
  }

  function triggerCondense(control, opts){
    debug('Condense Trigger: '+control.html());
    var orig = control.parent(); // The original element will be the control's immediate parent.
    var condensed = orig.next(); // The condensed element will be the original immediate next sibling.
    condensed.show();
    var con_w  = condensed.width();
    var con_h = condensed.height();
    condensed.hide(); //briefly flashed the condensed element so we can get the target width/height
    var orig_w  = orig.width();
    var orig_h = orig.height();
    orig.animate({height:con_h, width:con_w, opacity: 1}, opts.lessSpeed, opts.easing,
      function(){
        orig.height(orig_h).width(orig_w).hide();
        condensed.show();
      });
  }

  function triggerExpand(control, opts){
    debug('Expand Trigger: '+control.html());
    var condensed = control.parent(); // The condensed element will be the control's immediate parent.
    var orig = condensed.prev(); // The original element will be the condensed immediate previous sibling.
    orig.show();
    var orig_w  = orig.width();
    var orig_h = orig.height();
    orig.width(condensed.width()+"px").height(condensed.height()+"px");
    condensed.hide();
    orig.animate({height:orig_h, width:orig_w, opacity: 1}, opts.moreSpeed, opts.easing);
    if(condensed.attr('id')){
      var idAttr = condensed.attr('id');
      condensed.attr('id','condensed_'+idAttr);
      orig.attr('id',idAttr);
    }
  }

  /**
   * private function for debugging
   */
  function debug($obj) {if (window.console && window.console.log){window.console.log($obj);}};

  // plugin defaults
  $.fn.condense.defaults = {
    condensedLength: 200,
    minTrail: 20,
    delim: " ",
    moreText: "[more]",
    lessText: "[less]",
    ellipsis: " ( ... )",
    moreSpeed: "normal",
    lessSpeed: "normal",
    easing: "linear"
  };

})(jQuery);
<|endoftext|>


<commit_before>;var Editable = {
    init: function(options, el) {
        this.options = $.extend({}, this.options, options);

        this.el = el;
        this.$el = $(el);

        this.alertID = null;
        this.isEditing = false;
        this.isSaving = false;
        this.originalValue = $.trim(this.$el.html());

        this.$alert = null;

        this._build();

        return this;
    },
    options: {
        url: null,
        method: null,
        args: {}
    },
    _build: function() {

        this.$el.addClass('editable-element');

        if (!this.$alert) {
            this.$alert = $('

');
            $('body').append(this.$alert);
        }

        var that = this;

        that.$el.click(function() {

            that.makeEditable();

        }).keydown(function(e) {

            if (e.which == 13) {
                // Enter pressed
                e.preventDefault();
                that.saveData();
            } else if (e.keyCode == 27) {
                // Escape pressed
                that.resetData();
            }
        }).blur(function(e) {
            that.saveData();
        });

    },
    makeEditable: function() {

        if (!this.isEditing) {
            var orig = $.trim(this.$el.html());

            this.$el.prop('contentEditable', true).addClass('editable-active');

            this.originalValue = orig;
            this.isEditing = true;

            this.setTextCursor();
        }
    },
    saveData: function() {
        var words = $.trim(this.$el.html()),
            field = this.$el.attr('data-fieldname'),
            that = this;

        if (words !== that.originalValue) {

            $.ajax({
                 url: that.options.url,
                 type: 'post',
                 data: $.extend({}, that.options.args, { method: that.options.method, field: field, value: words }),
                 dataType: 'json',
                 success: function(r) {
                    if (r.success) {
                        that.originalValue = words;
                        that.resetData();
                    } else {
                        that.$el.addClass('editable-error');
                        that.$el.isSaving = false;
                    }
                    if (r.message && r.message.length) {
                        that.showMessage(r.message);
                    }
                 }
            });
        } else {
            this.resetData();
        }
    },
    resetData: function() {
        this.isSaving = true;

        this.$el.removeClass('editable-active').removeClass('editable-error');
        this.$el.prop('contentEditable', false);
        this.isEditing = false;
        this.isSaving = false;

        this.$el.html(this.originalValue);

        if (this.$el.is(':focus')) {
            this.$el.blur();
        }
    },
    setTextCursor: function() {
        this.$el.focus();

        if (typeof window.getSelection !== "undefined" && typeof document.createRange !== "undefined") {
            var range = document.createRange();

            range.selectNodeContents(this.el);
            range.collapse(false);

            var sel = window.getSelection();

            sel.removeAllRanges();
            sel.addRange(range);

        } else if (typeof document.body.createTextRange !== "undefined") {
            var textRange = document.body.createTextRange();

            textRange.moveToElementText(this.el);
            textRange.collapse(false);
            textRange.select();
        }
    },
    showMessage: function(message) {

        var that = this;

        that.$alert.find('[data-html]').html(message);
        that.$alert.show();

        if (this.alertid) {
            clearTimeout(this.alertid);
        }

        this.alertid = setTimeout(function() {
            that.$alert.hide();
            that.alertid = null;
        }, 2500);

    }
};

if ( typeof Object.create !== "function" ) {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
};

$.fn.editable = function (url, method, args) {
    return this.each(function () {
        if (!$.data(this, 'editable')) {
            options = {
                url: url,
                method: method,
                args: args
            };
            $.data(this, 'editable', Object.create(Editable).init(options, this));
        }
    });
};

<commit_msg>Fix to alert message selector, add data-arguments<commit_after>;var Editable = {
    init: function(options, el) {
        this.options = $.extend({}, this.options, options);

        this.el = el;
        this.$el = $(el);

        this.alertID = null;
        this.isEditing = false;
        this.isSaving = false;
        this.originalValue = $.trim(this.$el.html());

        this.$alert = null;

        this._build();

        return this;
    },
    options: {
        url: null,
        method: null,
        args: {}
    },
    _build: function() {

        this.$el.addClass('editable-element');

        if (!this.$alert) {
            this.$alert = $('

');
            $('body').append(this.$alert);
        }

        var that = this;

        that.$el.click(function() {

            that.makeEditable();

        }).keydown(function(e) {

            if (e.which == 13) {
                // Enter pressed
                e.preventDefault();
                that.saveData();
            } else if (e.keyCode == 27) {
                // Escape pressed
                that.resetData();
            }
        }).blur(function(e) {
            that.saveData();
        });

    },
    makeEditable: function() {

        if (!this.isEditing) {
            var orig = $.trim(this.$el.html());

            this.$el.prop('contentEditable', true).addClass('editable-active');

            this.originalValue = orig;
            this.isEditing = true;

            this.setTextCursor();
        }
    },
    saveData: function() {
        var words = $.trim(this.$el.html()),
            field = this.$el.attr('data-fieldname'),
            additionalargs = this.$el.attr('data-arguments'),
            that = this;

        if (words !== that.originalValue) {

            var ajaxargs = $.extend({}, that.options.args, {
                method: that.options.method,
                value: words
            });

            if (field) {
                ajaxargs = $.extend({}, ajaxargs, { field: field });
            }

            if (additionalargs) {
                try {
                    var tmp = $.parseJSON(additionalargs);

                    ajaxargs = $.extend({}, ajaxargs, tmp);
                }
                catch(e) {
                    console.log("Invalid JSON in data-arguments for this element:", this.$el);
                }
            }

            $.ajax({
                 url: that.options.url,
                 type: 'post',
                 data: ajaxargs,
                 dataType: 'json',
                 success: function(r) {

                    if (r.success) {
                        that.originalValue = words;
                        that.resetData();
                    } else {
                        that.$el.addClass('editable-error');
                        that.$el.isSaving = false;
                    }
                    if (r.message && r.message.length) {
                        that.showMessage(r.message);
                    }
                 }
            });
        } else {
            this.resetData();
        }
    },
    resetData: function() {
        this.isSaving = true;

        this.$el.removeClass('editable-active').removeClass('editable-error');
        this.$el.prop('contentEditable', false);
        this.isEditing = false;
        this.isSaving = false;

        this.$el.html(this.originalValue);

        if (this.$el.is(':focus')) {
            this.$el.blur();
        }
    },
    setTextCursor: function() {
        this.$el.focus();

        if (typeof window.getSelection !== "undefined" && typeof document.createRange !== "undefined") {
            var range = document.createRange();

            range.selectNodeContents(this.el);
            range.collapse(false);

            var sel = window.getSelection();

            sel.removeAllRanges();
            sel.addRange(range);

        } else if (typeof document.body.createTextRange !== "undefined") {
            var textRange = document.body.createTextRange();

            textRange.moveToElementText(this.el);
            textRange.collapse(false);
            textRange.select();
        }
    },
    showMessage: function(message) {

        var that = this;

        that.$alert.find('.editable-alert-message').html(message);
        that.$alert.show();

        if (this.alertid) {
            clearTimeout(this.alertid);
        }

        this.alertid = setTimeout(function() {
            that.$alert.hide();
            that.alertid = null;
        }, 2500);

    }
};

if ( typeof Object.create !== "function" ) {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
};

$.fn.editable = function (url, method, args) {
    return this.each(function () {
        if (!$.data(this, 'editable')) {
            options = {
                url: url,
                method: method,
                args: args
            };
            $.data(this, 'editable', Object.create(Editable).init(options, this));
        }
    });
};
<|endoftext|>


<commit_before>/*!
 * jQuery GrewForm Plugin v0.0.2
 *
 * Copyright 2011, Artem Suschev
 * Grandcapital Ltd.
 *
 */

jQuery.fn.grewform = function(options){

    //this will allow selectors like 'input[value=foo]' to work with all jQuery versions
    jQuery('input,textarea').live('keyup change',function(e) {
        var ignore_codes= [33,34,36,35,45,38,40,37,39]//arrows and others
        if(e.keyCode && jQuery.inArray(e.keyCode,ignore_codes)<0)//ignore this keyUps to let this keys work as expected
            jQuery(this).attr('value',this.value)
    });

    var form = jQuery(this)

    for (var rule_key in options)
    {
        var rule = new Rule(rule_key,form,options[rule_key])
    }

    //run in 300ms after keyup
    form.find('*').keyup(function(){
        var wait = setInterval(function(){
            clearInterval(wait)
            run_rules()
        },
        300)
    });

    //run on DOM loaded
    jQuery(document).ready(function(){
        run_rules()
    });

    //run after every change
    return form.find('*').change(function(){
        run_rules()
    });
};

function debug(str)
{
    console.log('jQuery.brForms >>> '+str)
}

function run_rules(){
    //debug(checking rules')

    //first run all unmatch actions
    for(var i in Rule.all)
            (function(rule){
                 var wait = setInterval(
                     function() {
                         if( !rule.form.find('*').is(":animated"))
                         {
                             clearInterval(wait);
                             if(rule.unmatches())
                             {
                                 rule.run_unmatch_actions()
                             }
                         }
                     },
                     200
                 )
            }(Rule.all[i]))

    //then run all math actions
    for(var i in Rule.all)
            (function(rule){
                 var wait = setInterval(
                     function() {
                         if( !rule.form.find('*').is(":animated"))
                         {
                             clearInterval(wait);
                             if(rule.matches())
                             {
                                    rule.run_match_actions()
                             }
                         }
                     },
                     200
                 )
            }(Rule.all[i]))
}

//constructor for rules
function Rule(selector,form,raw_rule){
    if ( typeof Rule.id == 'undefined' )
    {
        //unique id for rules
        Rule.id=0
        //special blip to mark DOM elements witch invoke rule actions
        Rule.blip_ptr = 'hgo_grewform_rule'
        //function for cascade unmatch
        Rule.unmtach_by_blip = function(blip){
                                   for(var k in Rule.all)
                                       if(Rule.all[k].blip === blip)
                                       {
                                           //debug('unmtach_by_blip '+blip)
                                           Rule.all[k].run_unmatch_actions()
                                       }
                               }

        //All rules (hello Django:])
        Rule.all = []
    }

    this.id = Rule.id++
    this.blip = Rule.blip_ptr+this.id
    this.selectors = arrayfy((''+selector).split('AND')) //selector of the rule
    jQuery.each(this.selectors,function(selectors){return function(k,v){selectors[k]=jQuery.trim(''+v)}}(this.selectors))//trim each selector
    this.selector = arr_to_selector(this.selectors) //selector of the rule
    this.trigged = false       //indicates is rule trigged or not
    this.match_actions = []
    this.unmatch_actions = []
    this.raw = raw_rule
    this.raw_selector = selector
    this.form = form

    //extract actions
    for(var action_key in raw_rule)
        generate_actions(action_key,form,this)

    //debug('created rule: '+this.selector +'['+ this.match_actions.length +'|'+this.unmatch_actions.length+']')

    this.matches = function(){
        for (var i in this.selectors)
        {
            var selector = this.selectors[i]
            if((!this.trigged))
            {
                 //('option:visible') always return nothing
                 if(this.form.find(selector).filter('option:first').length > 0)
                 {
                    if(this.form.find(selector).parent('select:visible:first').length == 0)
                        return false
                 }
                 else if(this.form.find(selector).filter(':visible:first').length == 0)
                     return false
            }
            else
            {
                return false
            }
        }
        return true
    }

    this.unmatches = function(){
        if(!this.trigged)
                return false

        for (var i in this.selectors)
        {
            var selector = this.selectors[i]
            if(this.form.find(selector+':first').length == 0)
                return true
        }

        return false
    }

    this.run_match_actions = function(){
        debug('match: '+this.raw_selector)

        this.trigged = true
        //mark elements that rule trigged, it's need for cascade actions
        this.form.find(this.selector).addClass(this.blip)
        for(var c in this.match_actions)
            this.match_actions[c].call(this)
    }
    this.run_match_actions.first_run = true

    this.run_unmatch_actions = function(){
        debug('unmatch: '+this.raw_selector)

        this.trigged = false
        jQuery('.'+this.blip).removeClass(this.blip)
        for(var c in this.unmatch_actions)
            this.unmatch_actions[c].call(this)
    }

    Rule.all.push(this)
}

function generate_actions(key,form,rule)
{
    //genereting different actions
    switch(key){
        case 'show':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('show action');
                        if(elems.is('option'))
                        {
                            elems.show();
                        }
                        else
                        {
                            elems.slideDown();
                        }
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('show action rollback');
                        cascade_unmatch(elems);
                        if(elems.is('option'))
                        {
                            elems.hide();
                        }
                        else
                        {
                            elems.slideUp();
                        }
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'hide':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('hide action');
                        if(elems.is('option'))
                        {
                            elems.hide();
                        }
                        else
                        {
                            elems.slideUp();
                        }
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('hide action rollback');
                        cascade_unmatch(elems);
                        if(elems.is('option'))
                        {
                            elems.show();
                        }
                        else
                        {
                            elems.slideDown();
                        }
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'set_value':
            rule.match_actions.push(
                function(values){
                    return function(){
                        //debug('value action');

                        var rule=this
                        jQuery.each(values,function(selector,value){
                            jQuery.each(rule.form.find(selector),function(){
                                var tagName = jQuery(this).tagName

                                if(tagName == 'select')
                                {
                                     elems.find('option').removeAttr('selected')
                                     elems.find('option [value='+value+']').attr('selected','selected')
                                }
                                else
                                    jQuery(this).val(value)
                            })
                        })
                    }
                }(rule.raw[key]))
            break
        case 'add_options':
            for (var selector in rule.raw[key])
            {
                var options = rule.raw[key][selector]

                if(typeof options == 'function')
                {
                    rule.match_actions.push(
                        function(e,selector,fn){
                             return function(){
                                 var options = fn()
                                 for(var v in options){
                                     //debug('add_options action')
                                     var selects = e.find(selector)
                                     jQuery('<option></option>').html(options[v])
                                                  .val(v)
                                                  .appendTo(selects);
                                 }

                                 //small trick with callable options:
                                 //we should determine what to remove from DOM right after we add it!
                                 if(this.run_match_actions.first_run){
                                     for(var v in options)
                                        this.unmatch_actions.push(
                                            function(e,selector,v){
                                                 return function(){
                                                     //debug('add_options action rollback');
                                                     cascade_unmatch(e.children(selector).children('option[value='+v+']'))
                                                     e.find(selector).children().remove('option[value='+v+']')
                                                 }
                                            }(form,selector,v)
                                     )
                                     //for adding unmatch actions only once!
                                     this.run_match_actions.first_run = false
                                 }
                             }
                        }(form,selector,options)
                    )
                }
                else{
                    for(var v in options){
                        rule.match_actions.push(
                            function(e,selector,v,h){
                                 return function(){
                                     //debug('add_options action')
                                     var selects = e.find(selector)
                                     jQuery('<option></option>').html(h)
                                                  .val(v)
                                                  .appendTo(selects);
                                 }
                            }(form,selector,v,options[v])
                        )
                        rule.unmatch_actions.push(
                            function(e,selector,v){
                                 return function(){
                                     //debug('add_options action rollback');
                                     cascade_unmatch(e.children(selector).children('option[value='+v+']'))
                                     e.find(selector).children().remove('option[value='+v+']')
                                 }
                            }(form,selector,v)
                        )
                    }
                }
            }
            break
        case 'disable':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('disable action');
                        elems.attr('disabled','disabled');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('disable action rollback');
                        cascade_unmatch(elems);
                        elems.removeAttr('disabled');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'enable':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('enable action');
                        elems.removeAttr('disabled');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('enable action rollback');
                        cascade_unmatch(elems);
                        elems.attr('disabled','disabled');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'check':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('check action');
                        elems.attr('checked','checked');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('check action rollback');
                        cascade_unmatch(elems);
                        elems.removeAttr('checked');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'uncheck':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('uncheck action');
                        elems.removeAttr('checked');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('uncheck action rollback');
                        cascade_unmatch(elems);
                        elems.attr('checked','checked');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'custom':
            rule.match_actions.push(
                function(fn,context){
                    return function(){fn.call(context)}
                }(rule.raw[key]['match'],jQuery(rule.selector))
            )

            rule.unmatch_actions.push(
                function(fn,context){
                    return function(){fn.call(context)}
                }(rule.raw[key]['unmatch'],jQuery(rule.selector))
            )
            break
    }
}

function cascade_unmatch(elements)
{
    jQuery.each(elements,function(k,v){
        if(jQuery(this).attr('class') !== undefined)
        {
            var classes = jQuery(this).attr('class').split(' ')
        }
        else
        {
            return;
        }

        jQuery.each(classes,function(k,v){
            Rule.unmtach_by_blip(v)
        })
        cascade_unmatch(elements.children())
    })
}

function arrayfy(obj)
{
    if(obj.constructor != Array)
        return [obj];

    return obj;
}

function arr_to_selector(arr)
{
    res = ''
    for(var i in arr)
        res += jQuery.trim(''+arr[i]) + ','

    return res
}<commit_msg>Fixed: removed console.log() calls<commit_after>/*!
 * jQuery GrewForm Plugin v0.0.2
 *
 * Copyright 2011, Artem Suschev
 * Grandcapital Ltd.
 *
 * Licensed under the MIT license (license.txt)
 */

jQuery.fn.grewform = function(options){

    //this will allow selectors like 'input[value=foo]' to work with all jQuery versions
    jQuery('input,textarea').live('keyup change',function(e) {
        var ignore_codes= [33,34,36,35,45,38,40,37,39]//arrows and others
        if(e.keyCode && jQuery.inArray(e.keyCode,ignore_codes)<0)//ignore this keyUps to let this keys work as expected
            jQuery(this).attr('value',this.value)
    });

    var form = jQuery(this)

    for (var rule_key in options)
    {
        var rule = new Rule(rule_key,form,options[rule_key])
    }

    //run in 300ms after keyup
    form.find('*').keyup(function(){
        var wait = setInterval(function(){
            clearInterval(wait)
            run_rules()
        },
        300)
    });

    //run on DOM loaded
    jQuery(document).ready(function(){
        run_rules()
    });

    //run after every change
    return form.find('*').change(function(){
        run_rules()
    });
};

function debug(str)
{
    //console.log('jQuery.brForms >>> '+str)
}

function run_rules(){
    //debug(checking rules')

    //first run all unmatch actions
    for(var i in Rule.all)
            (function(rule){
                 var wait = setInterval(
                     function() {
                         if( !rule.form.find('*').is(":animated"))
                         {
                             clearInterval(wait);
                             if(rule.unmatches())
                             {
                                 rule.run_unmatch_actions()
                             }
                         }
                     },
                     200
                 )
            }(Rule.all[i]))

    //then run all math actions
    for(var i in Rule.all)
            (function(rule){
                 var wait = setInterval(
                     function() {
                         if( !rule.form.find('*').is(":animated"))
                         {
                             clearInterval(wait);
                             if(rule.matches())
                             {
                                    rule.run_match_actions()
                             }
                         }
                     },
                     200
                 )
            }(Rule.all[i]))
}

//constructor for rules
function Rule(selector,form,raw_rule){
    if ( typeof Rule.id == 'undefined' )
    {
        //unique id for rules
        Rule.id=0
        //special blip to mark DOM elements witch invoke rule actions
        Rule.blip_ptr = 'hgo_grewform_rule'
        //function for cascade unmatch
        Rule.unmtach_by_blip = function(blip){
                                   for(var k in Rule.all)
                                       if(Rule.all[k].blip === blip)
                                       {
                                           //debug('unmtach_by_blip '+blip)
                                           Rule.all[k].run_unmatch_actions()
                                       }
                               }

        //All rules (hello Django:])
        Rule.all = []
    }

    this.id = Rule.id++
    this.blip = Rule.blip_ptr+this.id
    this.selectors = arrayfy((''+selector).split('AND')) //selector of the rule
    jQuery.each(this.selectors,function(selectors){return function(k,v){selectors[k]=jQuery.trim(''+v)}}(this.selectors))//trim each selector
    this.selector = arr_to_selector(this.selectors) //selector of the rule
    this.trigged = false       //indicates is rule trigged or not
    this.match_actions = []
    this.unmatch_actions = []
    this.raw = raw_rule
    this.raw_selector = selector
    this.form = form

    //extract actions
    for(var action_key in raw_rule)
        generate_actions(action_key,form,this)

    //debug('created rule: '+this.selector +'['+ this.match_actions.length +'|'+this.unmatch_actions.length+']')

    this.matches = function(){
        for (var i in this.selectors)
        {
            var selector = this.selectors[i]
            if((!this.trigged))
            {
                 //('option:visible') always return nothing
                 if(this.form.find(selector).filter('option:first').length > 0)
                 {
                    if(this.form.find(selector).parent('select:visible:first').length == 0)
                        return false
                 }
                 else if(this.form.find(selector).filter(':visible:first').length == 0)
                     return false
            }
            else
            {
                return false
            }
        }
        return true
    }

    this.unmatches = function(){
        if(!this.trigged)
                return false

        for (var i in this.selectors)
        {
            var selector = this.selectors[i]
            if(this.form.find(selector+':first').length == 0)
                return true
        }

        return false
    }

    this.run_match_actions = function(){
        debug('match: '+this.raw_selector)

        this.trigged = true
        //mark elements that rule trigged, it's need for cascade actions
        this.form.find(this.selector).addClass(this.blip)
        for(var c in this.match_actions)
            this.match_actions[c].call(this)
    }
    this.run_match_actions.first_run = true

    this.run_unmatch_actions = function(){
        debug('unmatch: '+this.raw_selector)

        this.trigged = false
        jQuery('.'+this.blip).removeClass(this.blip)
        for(var c in this.unmatch_actions)
            this.unmatch_actions[c].call(this)
    }

    Rule.all.push(this)
}

function generate_actions(key,form,rule)
{
    //genereting different actions
    switch(key){
        case 'show':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('show action');
                        if(elems.is('option'))
                        {
                            elems.show();
                        }
                        else
                        {
                            elems.slideDown();
                        }
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('show action rollback');
                        cascade_unmatch(elems);
                        if(elems.is('option'))
                        {
                            elems.hide();
                        }
                        else
                        {
                            elems.slideUp();
                        }
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'hide':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('hide action');
                        if(elems.is('option'))
                        {
                            elems.hide();
                        }
                        else
                        {
                            elems.slideUp();
                        }
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('hide action rollback');
                        cascade_unmatch(elems);
                        if(elems.is('option'))
                        {
                            elems.show();
                        }
                        else
                        {
                            elems.slideDown();
                        }
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'set_value':
            rule.match_actions.push(
                function(values){
                    return function(){
                        //debug('value action');

                        var rule=this
                        jQuery.each(values,function(selector,value){
                            jQuery.each(rule.form.find(selector),function(){
                                var tagName = jQuery(this).tagName

                                if(tagName == 'select')
                                {
                                     elems.find('option').removeAttr('selected')
                                     elems.find('option [value='+value+']').attr('selected','selected')
                                }
                                else
                                    jQuery(this).val(value)
                            })
                        })
                    }
                }(rule.raw[key]))
            break
        case 'add_options':
            for (var selector in rule.raw[key])
            {
                var options = rule.raw[key][selector]

                if(typeof options == 'function')
                {
                    rule.match_actions.push(
                        function(e,selector,fn){
                             return function(){
                                 var options = fn()
                                 for(var v in options){
                                     //debug('add_options action')
                                     var selects = e.find(selector)
                                     jQuery('<option></option>').html(options[v])
                                                  .val(v)
                                                  .appendTo(selects);
                                 }

                                 //small trick with callable options:
                                 //we should determine what to remove from DOM right after we add it!
                                 if(this.run_match_actions.first_run){
                                     for(var v in options)
                                        this.unmatch_actions.push(
                                            function(e,selector,v){
                                                 return function(){
                                                     //debug('add_options action rollback');
                                                     cascade_unmatch(e.children(selector).children('option[value='+v+']'))
                                                     e.find(selector).children().remove('option[value='+v+']')
                                                 }
                                            }(form,selector,v)
                                     )
                                     //for adding unmatch actions only once!
                                     this.run_match_actions.first_run = false
                                 }
                             }
                        }(form,selector,options)
                    )
                }
                else{
                    for(var v in options){
                        rule.match_actions.push(
                            function(e,selector,v,h){
                                 return function(){
                                     //debug('add_options action')
                                     var selects = e.find(selector)
                                     jQuery('<option></option>').html(h)
                                                  .val(v)
                                                  .appendTo(selects);
                                 }
                            }(form,selector,v,options[v])
                        )
                        rule.unmatch_actions.push(
                            function(e,selector,v){
                                 return function(){
                                     //debug('add_options action rollback');
                                     cascade_unmatch(e.children(selector).children('option[value='+v+']'))
                                     e.find(selector).children().remove('option[value='+v+']')
                                 }
                            }(form,selector,v)
                        )
                    }
                }
            }
            break
        case 'disable':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('disable action');
                        elems.attr('disabled','disabled');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('disable action rollback');
                        cascade_unmatch(elems);
                        elems.removeAttr('disabled');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'enable':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('enable action');
                        elems.removeAttr('disabled');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('enable action rollback');
                        cascade_unmatch(elems);
                        elems.attr('disabled','disabled');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'check':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('check action');
                        elems.attr('checked','checked');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('check action rollback');
                        cascade_unmatch(elems);
                        elems.removeAttr('checked');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'uncheck':
            rule.match_actions.push(
                function(elems){
                    return function(){
                        //debug('uncheck action');
                        elems.removeAttr('checked');
                    }
                }(form.find(rule.raw[key]))
            )

            rule.unmatch_actions.push(
                function(elems){
                    return function(){
                        //debug('uncheck action rollback');
                        cascade_unmatch(elems);
                        elems.attr('checked','checked');
                    }
                }(form.find(rule.raw[key]))
            )
            break
        case 'custom':
            rule.match_actions.push(
                function(fn,context){
                    return function(){fn.call(context)}
                }(rule.raw[key]['match'],jQuery(rule.selector))
            )

            rule.unmatch_actions.push(
                function(fn,context){
                    return function(){fn.call(context)}
                }(rule.raw[key]['unmatch'],jQuery(rule.selector))
            )
            break
    }
}

function cascade_unmatch(elements)
{
    jQuery.each(elements,function(k,v){
        if(jQuery(this).attr('class') !== undefined)
        {
            var classes = jQuery(this).attr('class').split(' ')
        }
        else
        {
            return;
        }

        jQuery.each(classes,function(k,v){
            Rule.unmtach_by_blip(v)
        })
        cascade_unmatch(elements.children())
    })
}

function arrayfy(obj)
{
    if(obj.constructor != Array)
        return [obj];

    return obj;
}

function arr_to_selector(arr)
{
    res = ''
    for(var i in arr)
        res += jQuery.trim(''+arr[i]) + ','

    return res
}<|endoftext|>


<commit_before>/*!
 * jQuery Panelize - v0.8.2 - 2013-11-01
 * Image navigation with image maps
 *
 * (c) 2013 R.E. Warner @belovedleader
 * MIT Licensed.
 *
 * http://russellbits.github.io/panelize
 * http://github.com/russellbits/panelize
*/

jQuery.fn.panelize = function( options ) {

	var defaults = {
		container: this, // this should be used as opposed to explicit jquery references
		fullImageStart: true,
		panelViewerID : '#panelViewer',
		showNextBtnID : '#nextPanelBtn',
		showPrevBtnID : '#prevPanelBtn',
		resetBtnID: '#resetBtn'
	};

	var settings = $.extend( {}, defaults, options );

	var panels = {
		panelIndex: 0,
		panel: [],
		viewerWidth: this.width(),
		viewerHeight: this.height()
	};

	function Panel(x1, y1, x2, y2) {
		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
		this.width = this.x2 - this.x1;
		this.height = this.y2 - this.y1;
	}

	// Intialize the panels in the comic
	// Assign area tag coordinates to the panels object (left,top,right,bottom)
	$(this).find('area').each(function(i) {
		if($(this).attr("target")!=null||$(this).attr("target")=="") {}
		else {
			var coords = $(this).attr('coords');
			var coordsStrArray = coords.split(',');
			panels.panel.push(new Panel(
				coordsStrArray[0],
				coordsStrArray[1],
				coordsStrArray[2],
				coordsStrArray[3]
			));
		}
	});

	var comic = $(settings.panelViewerID).find('img:first');

	/**
	* Start with full page option
	* 	The full scaled page will show, unless the user opts not to,
	* 	in which case, the first panel will zoom and show
	**/
	if(settings.fullImageStart) {
		var xOffset = 0;
		var yOffset = 0;

		// Add additional coordinates to return to full image
		panels.panel.unshift(new Panel(
			0,
			0,
			comic.width(),
			comic.height()
		));

		if(comic.width() > comic.height()) {
			scaleFactor = panels.viewerWidth/comic.width();
			yOffset = Math.floor(((panels.viewerHeight-(comic.height()*scaleFactor))/2));
		} else {
			scaleFactor = panels.viewerHeight/comic.height();
			xOffset = Math.floor(((panels.viewerWidth-(comic.width()*scaleFactor))/2));
		}
		console.log("Scaling to full scale image.");
		comic.css({transformOrigin:'0px 0px'})
			.animate({left:xOffset,top:yOffset,scale:scaleFactor});

		panels.panelIndex = 0;

	} else {
		panels.panelIndex = -1;
		transformPanel();
	}

	/**
	* MAIN TRANSFORM FUNCTION
	**/
	function transformPanel(dir) {
		// If previous,
		if(dir=='prev') {
			panels.panelIndex -= 1;
		} else {
			panels.panelIndex += 1;
		}

		// Reset to first panel when index hits maximum
		if(panels.panelIndex == panels.panel.length || panels.panelIndex < 0) {
			panels.panelIndex = 0;
		}

		console.log('Current index: '+panels.panelIndex);

		// Assign local variables
		var panelTop = panels.panel[panels.panelIndex].y1;
		var panelLeft = panels.panel[panels.panelIndex].x1;
		var panelWidth = panels.panel[panels.panelIndex].width;
		var panelHeight = panels.panel[panels.panelIndex].height;
		var panelBottom = panels.panel[panels.panelIndex].y2;
		var panelRight = panels.panel[panels.panelIndex].x2;
		var offsetIsX;
		var yOffset = 0;
		var xOffset = 0;
		var portraitMode = true;

		// Check if we are dealing with a portrait or landscape viewer
		if(panels.viewerWidth > panels.viewerHeight) {
			portraitMode = true;
		} else {
			portraitMode = false;
		}

		if(panelWidth > panelHeight || portraitMode==false) {
			var usingWidthAsMax = true;
			scaleFactor = panels.viewerWidth/panelWidth;
			offsetIsX = true;
		} else {
			var usingWidthAsMax = false;
			scaleFactor = panels.viewerHeight/panelHeight;
			offsetIsX = false;
		}

		var scaledPanelHeight = Math.floor(panelHeight*scaleFactor);
		var scaledPanelWdith = Math.floor(panelWidth*scaleFactor);

		// need to divide the offest by the scalefactor?
		if(offsetIsX) {
			yOffset = Math.floor((panels.viewerHeight-scaledPanelHeight)/2);
		} else {
			xOffset = Math.floor((panels.viewerWidth-scaledPanelWdith)/2);
		}

		console.log("Panel location: top:"+panelTop+
			', left:'+panelLeft+
			', bottom: '+panelBottom+
			', right:'+panelRight+
			', width: '+panelWidth+
			', height: '+panelHeight+
			', scalefactor: '+scaleFactor);

		// Perform the actual transformation
		var Xmove = -panelLeft+xOffset;
		var Ymove = -panelTop+yOffset;

		comic.css({transformOrigin:'0px 0px'})
			.animate({left:Xmove*scaleFactor,top:Ymove*scaleFactor,scale:scaleFactor});

	}

	/**
	* Interactive functions (UI)
	**/
	$(settings.showNextBtnID).click(function(event) {
		console.log('Next button activated');
		event.preventDefault();
		transformPanel('next');
	});

	$(settings.showPrevBtnID).click(function(event) {
		console.log('Previous button activated');
		event.preventDefault();
		transformPanel('prev');
	});

};<commit_msg>Added default setting to panelize object.<commit_after>/*!
 * jQuery Panelize - v0.8.2 - 2013-11-01
 * Image navigation with image maps
 *
 * (c) 2013 R.E. Warner @belovedleader
 * MIT Licensed.
 *
 * http://russellbits.github.io/panelize
 * http://github.com/russellbits/panelize
*/

jQuery.fn.panelize = function( options ) {

	var defaults = {
		container: this, // this should be used as opposed to explicit jquery references
		fullImageStart: true,
		panelViewerID : '#panelViewer',
		showNextBtnID : '#nextPanelBtn',
		showPrevBtnID : '#prevPanelBtn',
		resetBtnID: '#resetBtn',
		nonsense: 'nonsense' //only added this setting to test my github skills
	};

	var settings = $.extend( {}, defaults, options );

	var panels = {
		panelIndex: 0,
		panel: [],
		viewerWidth: this.width(),
		viewerHeight: this.height()
	};

	function Panel(x1, y1, x2, y2) {
		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
		this.width = this.x2 - this.x1;
		this.height = this.y2 - this.y1;
	}

	// Intialize the panels in the comic
	// Assign area tag coordinates to the panels object (left,top,right,bottom)
	$(this).find('area').each(function(i) {
		if($(this).attr("target")!=null||$(this).attr("target")=="") {}
		else {
			var coords = $(this).attr('coords');
			var coordsStrArray = coords.split(',');
			panels.panel.push(new Panel(
				coordsStrArray[0],
				coordsStrArray[1],
				coordsStrArray[2],
				coordsStrArray[3]
			));
		}
	});

	var comic = $(settings.panelViewerID).find('img:first');

	/**
	* Start with full page option
	* 	The full scaled page will show, unless the user opts not to,
	* 	in which case, the first panel will zoom and show
	**/
	if(settings.fullImageStart) {
		var xOffset = 0;
		var yOffset = 0;

		// Add additional coordinates to return to full image
		panels.panel.unshift(new Panel(
			0,
			0,
			comic.width(),
			comic.height()
		));

		if(comic.width() > comic.height()) {
			scaleFactor = panels.viewerWidth/comic.width();
			yOffset = Math.floor(((panels.viewerHeight-(comic.height()*scaleFactor))/2));
		} else {
			scaleFactor = panels.viewerHeight/comic.height();
			xOffset = Math.floor(((panels.viewerWidth-(comic.width()*scaleFactor))/2));
		}
		console.log("Scaling to full scale image.");
		comic.css({transformOrigin:'0px 0px'})
			.animate({left:xOffset,top:yOffset,scale:scaleFactor});

		panels.panelIndex = 0;

	} else {
		panels.panelIndex = -1;
		transformPanel();
	}

	/**
	* MAIN TRANSFORM FUNCTION
	**/
	function transformPanel(dir) {
		// If previous,
		if(dir=='prev') {
			panels.panelIndex -= 1;
		} else {
			panels.panelIndex += 1;
		}

		// Reset to first panel when index hits maximum
		if(panels.panelIndex == panels.panel.length || panels.panelIndex < 0) {
			panels.panelIndex = 0;
		}

		console.log('Current index: '+panels.panelIndex);

		// Assign local variables
		var panelTop = panels.panel[panels.panelIndex].y1;
		var panelLeft = panels.panel[panels.panelIndex].x1;
		var panelWidth = panels.panel[panels.panelIndex].width;
		var panelHeight = panels.panel[panels.panelIndex].height;
		var panelBottom = panels.panel[panels.panelIndex].y2;
		var panelRight = panels.panel[panels.panelIndex].x2;
		var offsetIsX;
		var yOffset = 0;
		var xOffset = 0;
		var portraitMode = true;

		// Check if we are dealing with a portrait or landscape viewer
		if(panels.viewerWidth > panels.viewerHeight) {
			portraitMode = true;
		} else {
			portraitMode = false;
		}

		if(panelWidth > panelHeight || portraitMode==false) {
			var usingWidthAsMax = true;
			scaleFactor = panels.viewerWidth/panelWidth;
			offsetIsX = true;
		} else {
			var usingWidthAsMax = false;
			scaleFactor = panels.viewerHeight/panelHeight;
			offsetIsX = false;
		}

		var scaledPanelHeight = Math.floor(panelHeight*scaleFactor);
		var scaledPanelWdith = Math.floor(panelWidth*scaleFactor);

		// need to divide the offest by the scalefactor?
		if(offsetIsX) {
			yOffset = Math.floor((panels.viewerHeight-scaledPanelHeight)/2);
		} else {
			xOffset = Math.floor((panels.viewerWidth-scaledPanelWdith)/2);
		}

		console.log("Panel location: top:"+panelTop+
			', left:'+panelLeft+
			', bottom: '+panelBottom+
			', right:'+panelRight+
			', width: '+panelWidth+
			', height: '+panelHeight+
			', scalefactor: '+scaleFactor);

		// Perform the actual transformation
		var Xmove = -panelLeft+xOffset;
		var Ymove = -panelTop+yOffset;

		comic.css({transformOrigin:'0px 0px'})
			.animate({left:Xmove*scaleFactor,top:Ymove*scaleFactor,scale:scaleFactor});

	}

	/**
	* Interactive functions (UI)
	**/
	$(settings.showNextBtnID).click(function(event) {
		console.log('Next button activated');
		event.preventDefault();
		transformPanel('next');
	});

	$(settings.showPrevBtnID).click(function(event) {
		console.log('Previous button activated');
		event.preventDefault();
		transformPanel('prev');
	});

};<|endoftext|>


<commit_before>(function ($, undefined) {

  var BLOCK_TAGS = ['table', 'thead', 'tbody', 'tfoot', 'tr', 'col', 'colgroup', 'object', 'embed', 'param', 'ol', 'ul', 'dl', 'blockquote', 'select', 'optgroup', 'option', 'textarea', 'script', 'style'];

  function setText(element, text) {
    if (element.innerText) {
      element.innerText = text;
    } else if (element.nodeValue) {
      element.nodeValue = text;
    } else if (element.textContent) {
      element.textContent = text;
    } else {
      return false;
    }
  }

  function truncateTextContent($element, $rootNode, $after, options) {
    var element = $element[0];
    var original = $element.text();

    var maxChunk = '';
    var mid, chunk;
    var low = 0;
    var high = original.length;

    // Binary Search
    while (low <= high) {
      mid = low + ((high - low) >> 1); // Integer division

      chunk = $.trim(original.substr(0, mid + 1)) + options.ellipsis;
      setText(element, chunk);

      if ($rootNode.height() > options.maxHeight) {
        high = mid - 1;
      } else {
        low = mid + 1;
        maxChunk = maxChunk.length > chunk.length ? maxChunk : chunk;
      }
    }

    if (maxChunk.length > 0) {
      setText(element, maxChunk);
      return true;
    } else {

      // Backtrack
      var $parent = $element.parent();
      $element.remove();

      var afterLength = $after ? $after.length : 0;

      if ($parent.contents().length > afterLength) {

        var $n = $parent.contents().eq(-1 - afterLength);
        return truncateTextContent($n, $rootNode, $after, options);

      } else {

        var $prev = $parent.prev();
        var $e = $prev.contents().eq(-1);
        var e = $e[0];

        if (e) {
          setText(e, $e.text() + options.ellipsis);
          $parent.remove();

          if ($after.length) {
            $prev.append($after);
          }
          return true;
        }
      }
      return false;
    }
  }

  function truncateNestedNode($element, $rootNode, $after, options) {
    var element = $element[0];

    var $children = $element.contents();
    var $child, child;

    var index = 0;
    var length = $children.length;
    var truncated = false;

    $element.empty();

    for (; index < length && !truncated; index++) {

      $child = $children.eq(index);
      child = $child[0];

      if (child.nodeType === 8) { // comment node
        continue;
      }

      element.appendChild(child);

      if ($after.length) {
        if ($.inArray(element.tagName.toLowerCase(), BLOCK_TAGS) >= 0) {
          $element.after($after);
        } else {
          $element.append($after);
        }
      }

      if ($rootNode.height() > options.maxHeight) {
        if (child.nodeType === 3) { // text node
          truncated = truncateTextContent($child, $rootNode, $after, options);
        } else {
          truncated = truncateNestedNode($child, $rootNode, $after, options);
        }
      }

      if (!truncated && $after.length) { $after.remove(); }

    }

    return truncated;
  }

  function Truncate(element, options) {
    this.element = element;
    this.$element = $(element);

    this._name = 'truncate';
    this._defaults = {
      lines: 1,
      ellipsis: '… ',
      showMore: 'More',
      showLess: 'Less'
    };

    this.options = $.extend({}, this._defaults, options);
    this.options.maxHeight = parseInt(this.options.lines, 10) * parseInt(this.options.lineHeight, 10);

    this.$after = $(this.options.showMore, this.$element);

    this.original = element.innerHTML;
    this.cached = null;

    this.update();
  }

  Truncate.prototype = {

    update: function (html) {
      // Update HTML if provided, otherwise default to current inner HTML.
      if (html) {
        this.original = this.element.innerHTML = html;
      }

      var $wrap = this.$element.wrapInner('
').children();
      $wrap.css({
        height : 'auto',
        width  : 'auto',
        border : 'none',
        padding: 0,
        margin : 0
      });

      // Check if already meets height requirement
      if ($wrap.height() > this.options.maxHeight) {
        truncateNestedNode($wrap, $wrap, this.$after, this.options);
        this.cachedHTML = this.element.innerHTML;
      }

      $wrap.replaceWith($wrap.contents());
    },

    expand: function () {
      this.element.innerHTML = this.originalHTML + this.options.showLess;
    },

    collapse: function () {
      this.element.innerHTML = this.cachedHTML;
    }

  };

  // Lightweight plugin wrapper preventing multiple instantiations
  $.fn.truncate = function (options) {
    return this.each(function () {
      if (!$.data(this, 'jquery-truncate')) {
        $.data(this, 'jquery-truncate', new Truncate(this, options));
      }
    });
  };

})(jQuery);
<commit_msg>Add docs<commit_after>(function ($, undefined) {

  var BLOCK_TAGS = ['table', 'thead', 'tbody', 'tfoot', 'tr', 'col', 'colgroup', 'object', 'embed', 'param', 'ol', 'ul', 'dl', 'blockquote', 'select', 'optgroup', 'option', 'textarea', 'script', 'style'];

  function setText(element, text) {
    if (element.innerText) {
      element.innerText = text;
    } else if (element.nodeValue) {
      element.nodeValue = text;
    } else if (element.textContent) {
      element.textContent = text;
    } else {
      return false;
    }
  }

  /* Truncates the text content of a node using binary search.
   *
   * textNode - The node to truncate.
   * rootNode - The root node (ancestor of the textNode) to measure the truncated height.
   * options  - An object containing:
   *            ellipsis  - The ellipsis string to append at the end of the truncation.
   *            maxHeight - The maximum height for the root node.
   *
   * Returns nothing.
   */
  function truncateTextContent($element, $rootNode, $after, options) {
    var element = $element[0];
    var original = $element.text();

    var maxChunk = '';
    var mid, chunk;
    var low = 0;
    var high = original.length;

    // Binary Search
    while (low <= high) {
      mid = low + ((high - low) >> 1); // Integer division

      chunk = $.trim(original.substr(0, mid + 1)) + options.ellipsis;
      setText(element, chunk);

      if ($rootNode.height() > options.maxHeight) {
        high = mid - 1;
      } else {
        low = mid + 1;
        maxChunk = maxChunk.length > chunk.length ? maxChunk : chunk;
      }
    }

    if (maxChunk.length > 0) {
      setText(element, maxChunk);
      return true;
    } else {

      // Backtrack
      var $parent = $element.parent();
      $element.remove();

      var afterLength = $after ? $after.length : 0;

      if ($parent.contents().length > afterLength) {

        var $n = $parent.contents().eq(-1 - afterLength);
        return truncateTextContent($n, $rootNode, $after, options);

      } else {

        var $prev = $parent.prev();
        var $e = $prev.contents().eq(-1);
        var e = $e[0];

        if (e) {
          setText(e, $e.text() + options.ellipsis);
          $parent.remove();

          if ($after.length) {
            $prev.append($after);
          }
          return true;
        }
      }
      return false;
    }
  }

  function truncateNestedNode($element, $rootNode, $after, options) {
    var element = $element[0];

    var $children = $element.contents();
    var $child, child;

    var index = 0;
    var length = $children.length;
    var truncated = false;

    $element.empty();

    for (; index < length && !truncated; index++) {

      $child = $children.eq(index);
      child = $child[0];

      if (child.nodeType === 8) { // comment node
        continue;
      }

      element.appendChild(child);

      if ($after.length) {
        if ($.inArray(element.tagName.toLowerCase(), BLOCK_TAGS) >= 0) {
          $element.after($after);
        } else {
          $element.append($after);
        }
      }

      if ($rootNode.height() > options.maxHeight) {
        if (child.nodeType === 3) { // text node
          truncated = truncateTextContent($child, $rootNode, $after, options);
        } else {
          truncated = truncateNestedNode($child, $rootNode, $after, options);
        }
      }

      if (!truncated && $after.length) { $after.remove(); }

    }

    return truncated;
  }

  /* Public: Creates an instance of Truncate.
   *
   * element - A DOM element to be truncated.
   * options - An Object literal containing setup options.
   *
   * Examples:
   *
   *   var element = document.createElement('span');
   *   element.innerHTML = 'This is
odd.';
   *   var truncated = new Truncate(element, {
   *     lines: 1,
   *     lineHeight: 16,
   *     showMore: 'Show More',
   *     showLess: 'Show Less'
   *   });
   *
   *   // Update HTML
   *   truncated.update('This is not very odd.');
   *
   *   // Undo truncation
   *   truncated.expand();
   *
   *   // Redo truncation
   *   truncated.collapse();
   */
  function Truncate(element, options) {
    this.element = element;
    this.$element = $(element);

    this._name = 'truncate';
    this._defaults = {
      lines: 1,
      ellipsis: '… ',
      showMore: 'More',
      showLess: 'Less'
    };

    this.options = $.extend({}, this._defaults, options);
    this.options.maxHeight = parseInt(this.options.lines, 10) * parseInt(this.options.lineHeight, 10);

    this.$after = $(this.options.showMore, this.$element);

    this.original = element.innerHTML;
    this.cached = null;

    this.update();
  }

  Truncate.prototype = {

    /* Public: Updates the inner HTML of the element and re-truncates.
     *
     * newHTML - The new HTML.
     *
     * Returns nothing.
     */
    update: function (html) {
      // Update HTML if provided, otherwise default to current inner HTML.
      if (html) {
        this.original = this.element.innerHTML = html;
      }

      var $wrap = this.$element.wrapInner('
').children();
      $wrap.css({
        height : 'auto',
        width  : 'auto',
        border : 'none',
        padding: 0,
        margin : 0
      });

      // Check if already meets height requirement
      if ($wrap.height() > this.options.maxHeight) {
        truncateNestedNode($wrap, $wrap, this.$after, this.options);
        this.cachedHTML = this.element.innerHTML;
      }

      $wrap.replaceWith($wrap.contents());
    },

    /* Public: Expands the element to show content in full.
     *
     * Returns nothing.
     */
    expand: function () {
      this.element.innerHTML = this.originalHTML + this.options.showLess;
    },

    /* Public: Collapses the element to the truncated state.
     * Uses the cached HTML from .update().
     *
     * Returns nothing.
     */
    collapse: function () {
      this.element.innerHTML = this.cachedHTML;
    }

  };

  // Lightweight plugin wrapper preventing multiple instantiations
  $.fn.truncate = function (options) {
    return this.each(function () {
      if (!$.data(this, 'jquery-truncate')) {
        $.data(this, 'jquery-truncate', new Truncate(this, options));
      }
    });
  };

})(jQuery);
<|endoftext|>