<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

let instance = null;

const log = require('./logger.js')(__filename);

const Promise = require('bluebird');
const request = require('request');
const rp = require('request-promise');
const SysManager = require('./SysManager.js');
const sysManager = new SysManager('info');

const rclient = require('../util/redis_manager.js').getRedisClient()

const bone = require("../lib/Bone.js");
const IntelTool = require('./IntelTool');
const intelTool = new IntelTool();

const A_WEEK = 3600 * 24 * 7;

/* malware, botnet, spam, phishing, malicious activity, blacklist, dnsbl */
const IGNORED_TAGS = ['dnsbl', 'spam'];

module.exports = class {
    constructor(loglevel) {
        if (instance == null) {
            instance = this;
        }
        return instance;
    }

    action(action, ip, callback) {
        if (action === "ignore") {
            rclient.hmset("intel:action:"+ip, {'ignore':true}, (err)=> {
                callback(err,null);
            });
        } else if (action === "unignore") {
            rclient.hmset("intel:action:"+ip, {'ignore':false}, (err)=> {
                callback(err,null);
            });
        } else if (action === "block") {
        } else if (action === "unblock") {
        } else if (action === "support") {
        }

      bone.intel(ip, "", action, {});
    }

    async cacheLookupAsync(dest, origin) {
      let result;
      try {
        result = await rclient.getAsync("cache.intel:" + origin + ":" + dest);
      } catch (err) {
        // null
      }

      log.info("Cache lookup for", dest, ", result:", result);
      return result;
    }

    cachelookup(ip, origin, callback) {
        rclient.get("cache.intel:" + origin + ":" + ip, (err, result) => {
            callback(err, result);
        });
    }

    cacheAdd(ip, origin, value) {
        if (value == null) {
            value = "none";
        }
        rclient.set("cache.intel:" + origin + ":" + ip, value, (err, result) => {
          rclient.expireat("cache.intel:" + origin + ":" + ip, this.currentTime() + A_WEEK);
        });
    }

    currentTime() {
      return Math.round(Date.now() / 1000);
    }

    async lookupDomain(domain, ip, flowObj) {
      if (!domain || domain === "firewalla.com") {
        return;
      }

      if (await this.isIgnored(domain)) {
        log.info("Ignored domain:", domain, "skip...");
        return;
      }

      log.info("FlowObj:", flowObj);

      let intel = flowObj.intel;

      if (!intel.category) {
        log.info("No intel for domain", domain, "look up from cloud...");
        intel = await this._lookupDomain(domain, ip);
      } else {
        log.info("Intel for domain", domain, " exists in flowObj");
        if (intel.cc) {
          try {
            intel.cc = JSON.parse(intel.cc)[0];
          } catch (err) {
            log.warn("Error when parsing info.cc:", intel.cc, err);
          }
        }
      }
      log.info(`Intel for domain ${domain} is`, intel);

      return intel;
    }

    async isIgnored(target) {
      let data = await rclient.hgetallAsync("intel:action:" + target);
      log.info("Ignore check for domain:", target, " is", data);
      return data && data.ignore;
    }

    async _lookupDomain(domain, ip) {
      let cloudIntel;
      try {
        cloudIntel = await intelTool.checkIntelFromCloud([ip], [domain], 'out');
      } catch (err) {
        log.info("Error when check intel from cloud", err);
      }
      log.info("Cloud intel for ", domain, "is: ", cloudIntel);

      return this.processCloudIntel(cloudIntel[0]);
    }

  processCloudIntel(cloudIntel) {
    if (!cloudIntel) {
      return;
    }

    let intel = {};
    // check if the host matches the result from cloud
    // FIXME: ignore IP check because intel result from cloud does
    // NOT have "ip" all the time.
    if (cloudIntel.apps) {
      intel.apps = JSON.stringify(cloudIntel.apps);
      let keys = Object.keys(cloudIntel.apps);
      if (keys && keys[0]) {
        intel.app = keys[0];
      }
    }

    if (cloudIntel.c) {
      intel.category = cloudIntel.c;
    }

    if (cloudIntel.action && cloudIntel.action.block) {
      intel.action = "block"
    }

    if (cloudIntel.s) {
      intel.s = cloudIntel.s;
    }

    if (cloudIntel.t) {
      intel.t = cloudIntel.t;
    }

    if (cloudIntel.cc) {
      try {
        cloudIntel.cc = JSON.parse(cloudIntel.cc);
        intel.cc = cloudIntel.cc[0];
      } catch (err) {
        intel.cc = cloudIntel.cc;
        log.warn("Error when parsing info.cc:", cloudIntel.cc, err);
      }
    }
    return intel;
  }

    async lookup(ip, intel) {
      if (!ip || ip === "8.8.8.8" || sysManager.isLocalIP(ip)) {
        return;
      }

      let data = await rclient.hgetallAsync("intel:action:" + ip);
      if (data && data.ignore) {
        log.info("Intel:Lookup:Ignored", ip);
        return;
      }

      let result = await this.cacheLookupAsync(ip, "cymon");
      if (result && result !== "none") {
        let lobj = await this._location(ip);
        let obj = JSON.parse(result);
        if (!obj || obj.count === 0) {
          obj = {}
          obj.lobj = lobj;
          obj = this._packageIntel(ip, obj, intel);
          return obj;
        } else {
          obj.lobj = lobj;
          log.info("Intel:Location", ip, obj.lobj);
          this._packageCymon(ip, obj);
          return obj;
        }
      } else {
        return await this._lookup(ip, intel);
      }
    }

    _packageIntel(ip, obj, intel) {
        let weburl = "https://intel.firewalla.com/";
        log.info("IntelManger:PackageIntel:",ip,JSON.stringify(intel,null,2));
        if (intel == null) {
            return null;
        }
        if (intel.t) {
            obj.count = Math.abs(intel.t/10);
        } else {
            obj.count = 4;
        }
        if (intel.s) {
            obj.severityscore = intel.s;
        } else {
            obj.severityscore = 20;
        }
        obj.summary = "";
        obj.weburl = weburl;
        if (intel.cc) {
            try {
                obj.tags = JSON.parse(intel.cc);
            } catch(e) {
            }
        }
        log.info("IntelManger:PackageIntel:Done",ip,JSON.stringify(intel,null,2),JSON.stringify(obj,null,2));
        return obj;
    }

    _packageCymon(ip, obj) {
        let weburl = "https://cymon.io/" + ip;
        log.info("INFO:------ Intel Information", obj.count);

        let results = obj.results.filter(x => !IGNORED_TAGS.includes(x.tag));
        obj.count = results.length;
        let summary = obj.count + " reported this IP.\n";

        let tags = {};
        for (let r of obj.results) {
            if (r.tag) {
                if (tags[r.tag] == null) {
                    tags[r.tag] = {
                        tag: r.tag,
                        count: 1
                    };
                } else {
                    tags[r.tag].count += 1;
                }
            }
        }

        let tagArray = [], tagCount = 0, severity = 0;
        for (let i in tags) {
            let tag = tags[i];
            tagArray.push(tag);
            if (i.includes("malicious")) {
              severity += tag.count * 3;
            } else if (i.includes("malware")) {
              severity += tag.count * 3;
            } else if (i == "blacklist") {
              severity += tag.count * 3;
            } else {
              severity += 1;
            }
            tagCount += tag.count;
        }

        tagArray.sort((a, b) => b.count - a.count);

        obj.severityscore = severity;
        obj.summary = summary;
        obj.weburl = weburl;
        obj.tags = tagArray;

        if (obj.tags && obj.tags.length > 0) {
          const reasonize = (tag) => `${tag.tag} - ${Math.round(tag.count / tagCount * 100)}%`;
          let reason = "Possibility: ";
            let first = true;
            for (let tag of obj.tags) {
                if (first) {
                    reason += reasonize(tag);
                    first = false;
                } else {
                    reason += ", " + reasonize(tag);
                }
            }
            obj.reason = reason;
        }
    }

    /* curl ipinfo.io/98.124.243.43/
{
  "ip": "98.124.243.43",
  "hostname": "No Hostname",
  "city": "Kirkland",
  "region": "Washington",
  "country": "US",
  "loc": "47.6727,-122.1873",
  "org": "AS21740 eNom, Incorporated",
  "postal": "98033"
 */
  async _location(ip) {
    log.info("Looking up location:", ip);

    let cached = await this.cacheLookupAsync(ip, "ipinfo");

    if (cached) {
      return JSON.parse(cached);
    }

    let ipinfo = await Promise.join(
      this._ipInfoFromBone(ip),
      this._ipInfoFromIpinfo(ip),
      (info1, info2) => Object.assign(info1 ? info1 : {}, info2)
    );

    log.info("Merged ipinfo is:", ipinfo);

    this.cacheAdd(ip, "ipinfo", JSON.stringify(ipinfo));

    return ipinfo;
  }

  async _ipInfoFromBone(ip) {
    let result = await bone.intelFinger(ip);
    if (result) {
      log.info("ipInfo from bone is:", result.ipinfo);
      return result.ipinfo;
    }
    log.info("ipInfo from bone is:", null);
    return null;
  }

  async _ipInfoFromIpinfo(ip) {
    const options = {
      uri: "https://ipinfo.io/" + ip,
      method: 'GET',
      family: 4
      // Authorization: 'Token dc30fcd03eddbd95b90bacaea5e5a44b1b60d2f5',
    };

    let body, result;
    try {
      body = await rp(options);
    } catch (err) {
      log.error("Error while requesting", options.uri, err);
      return;
    }

    try {
      result = JSON.parse(body);
    } catch (err) {
      log.error("Error when parse body:", body, err);
    }

    log.info("ipInfo from ipinfo is:", result);
    return result;
  }

  async _lookup(ip, intel) {
    let url = "https://cymon.io/api/nexus/v1/ip/" + ip + "/events?limit=100";

    let options = {
      uri: url,
      method: 'GET',
      family: 4
    };

    let lobj = await this._location(ip);

    let obj = {lobj};
    log.info("Intel:Location", ip, lobj);
    obj = this._packageIntel(ip, obj, intel);

    let body;
    try {
      body = await rp(options);
    } catch (err) {
      log.info(`Error while requesting ${url}`, err);

    }

    this.cacheAdd(ip, "cymon", body);

    let cobj = JSON.parse(body);
    if (cobj) {
      if (cobj.count === 0) {
        log.info("INFO:====== No Intel Information!!", ip, obj);
        return obj;
      } else {
        cobj.lobj = lobj;
        this._packageCymon(ip, cobj);
        return cobj;
      }
    } else {
      return obj;
    }
  }
}
<commit_msg>reduce logging<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

let instance = null;

const log = require('./logger.js')(__filename);

const Promise = require('bluebird');
const request = require('request');
const rp = require('request-promise');
const SysManager = require('./SysManager.js');
const sysManager = new SysManager('info');

const rclient = require('../util/redis_manager.js').getRedisClient()

const bone = require("../lib/Bone.js");
const IntelTool = require('./IntelTool');
const intelTool = new IntelTool();

const A_WEEK = 3600 * 24 * 7;

/* malware, botnet, spam, phishing, malicious activity, blacklist, dnsbl */
const IGNORED_TAGS = ['dnsbl', 'spam'];

module.exports = class {
    constructor(loglevel) {
        if (instance == null) {
            instance = this;
        }
        return instance;
    }

    action(action, ip, callback) {
        if (action === "ignore") {
            rclient.hmset("intel:action:"+ip, {'ignore':true}, (err)=> {
                callback(err,null);
            });
        } else if (action === "unignore") {
            rclient.hmset("intel:action:"+ip, {'ignore':false}, (err)=> {
                callback(err,null);
            });
        } else if (action === "block") {
        } else if (action === "unblock") {
        } else if (action === "support") {
        }

      bone.intel(ip, "", action, {});
    }

    async cacheLookupAsync(dest, origin) {
      let result;
      try {
        result = await rclient.getAsync("cache.intel:" + origin + ":" + dest);
      } catch (err) {
        // null
      }

      log.info("Cache lookup for", dest, ", result:", result);
      return result;
    }

    cachelookup(ip, origin, callback) {
        rclient.get("cache.intel:" + origin + ":" + ip, (err, result) => {
            callback(err, result);
        });
    }

    cacheAdd(ip, origin, value) {
        if (value == null) {
            value = "none";
        }
        rclient.set("cache.intel:" + origin + ":" + ip, value, (err, result) => {
          rclient.expireat("cache.intel:" + origin + ":" + ip, this.currentTime() + A_WEEK);
        });
    }

    currentTime() {
      return Math.round(Date.now() / 1000);
    }

    async lookupDomain(domain, ip, flowObj) {
      if (!domain || domain === "firewalla.com") {
        return;
      }

      if (await this.isIgnored(domain)) {
        log.info("Ignored domain:", domain, "skip...");
        return;
      }

      log.info("FlowObj:", flowObj);

      let intel = flowObj.intel;

      if (!intel.category) {
        log.info("No intel for domain", domain, "look up from cloud...");
        intel = await this._lookupDomain(domain, ip);
      } else {
        log.info("Intel for domain", domain, " exists in flowObj");
        if (intel.cc) {
          try {
            intel.cc = JSON.parse(intel.cc)[0];
          } catch (err) {
            log.warn("Error when parsing info.cc:", intel.cc, err);
          }
        }
      }
      log.info(`Intel for domain ${domain} is`, intel);

      return intel;
    }

    async isIgnored(target) {
      let data = await rclient.hgetallAsync("intel:action:" + target);
      log.info("Ignore check for domain:", target, " is", data);
      return data && data.ignore;
    }

    async _lookupDomain(domain, ip) {
      let cloudIntel;
      try {
        cloudIntel = await intelTool.checkIntelFromCloud([ip], [domain], 'out');
      } catch (err) {
        log.info("Error when check intel from cloud", err);
      }
      log.info("Cloud intel for ", domain, "is: ", cloudIntel);

      return this.processCloudIntel(cloudIntel[0]);
    }

  processCloudIntel(cloudIntel) {
    if (!cloudIntel) {
      return;
    }

    let intel = {};
    // check if the host matches the result from cloud
    // FIXME: ignore IP check because intel result from cloud does
    // NOT have "ip" all the time.
    if (cloudIntel.apps) {
      intel.apps = JSON.stringify(cloudIntel.apps);
      let keys = Object.keys(cloudIntel.apps);
      if (keys && keys[0]) {
        intel.app = keys[0];
      }
    }

    if (cloudIntel.c) {
      intel.category = cloudIntel.c;
    }

    if (cloudIntel.action && cloudIntel.action.block) {
      intel.action = "block"
    }

    if (cloudIntel.s) {
      intel.s = cloudIntel.s;
    }

    if (cloudIntel.t) {
      intel.t = cloudIntel.t;
    }

    if (cloudIntel.cc) {
      try {
        cloudIntel.cc = JSON.parse(cloudIntel.cc);
        intel.cc = cloudIntel.cc[0];
      } catch (err) {
        intel.cc = cloudIntel.cc;
        log.warn("Error when parsing info.cc:", cloudIntel.cc, err);
      }
    }
    return intel;
  }

    async lookup(ip, intel) {
      if (!ip || ip === "8.8.8.8" || sysManager.isLocalIP(ip)) {
        return;
      }

      let data = await rclient.hgetallAsync("intel:action:" + ip);
      if (data && data.ignore) {
        log.info("Intel:Lookup:Ignored", ip);
        return;
      }

      let result = await this.cacheLookupAsync(ip, "cymon");
      if (result && result !== "none") {
        let lobj = await this._location(ip);
        let obj = JSON.parse(result);
        if (!obj || obj.count === 0) {
          obj = {}
          obj.lobj = lobj;
          obj = this._packageIntel(ip, obj, intel);
          return obj;
        } else {
          obj.lobj = lobj;
          log.info("Intel:Location", ip, obj.lobj);
          this._packageCymon(ip, obj);
          return obj;
        }
      } else {
        return await this._lookup(ip, intel);
      }
    }

    _packageIntel(ip, obj, intel) {
        let weburl = "https://intel.firewalla.com/";
        log.info("IntelManger:PackageIntel:",ip,JSON.stringify(intel,null,2));
        if (intel == null) {
            return null;
        }
        if (intel.t) {
            obj.count = Math.abs(intel.t/10);
        } else {
            obj.count = 4;
        }
        if (intel.s) {
            obj.severityscore = intel.s;
        } else {
            obj.severityscore = 20;
        }
        obj.summary = "";
        obj.weburl = weburl;
        if (intel.cc) {
            try {
                obj.tags = JSON.parse(intel.cc);
            } catch(e) {
            }
        }
        log.info("IntelManger:PackageIntel:Done",ip,JSON.stringify(intel,null,2),JSON.stringify(obj,null,2));
        return obj;
    }

    _packageCymon(ip, obj) {
        let weburl = "https://cymon.io/" + ip;
        log.info("INFO:------ Intel Information", obj.count);

        let results = obj.results.filter(x => !IGNORED_TAGS.includes(x.tag));
        obj.count = results.length;
        let summary = obj.count + " reported this IP.\n";

        let tags = {};
        for (let r of obj.results) {
            if (r.tag) {
                if (tags[r.tag] == null) {
                    tags[r.tag] = {
                        tag: r.tag,
                        count: 1
                    };
                } else {
                    tags[r.tag].count += 1;
                }
            }
        }

        let tagArray = [], tagCount = 0, severity = 0;
        for (let i in tags) {
            let tag = tags[i];
            tagArray.push(tag);
            if (i.includes("malicious")) {
              severity += tag.count * 3;
            } else if (i.includes("malware")) {
              severity += tag.count * 3;
            } else if (i == "blacklist") {
              severity += tag.count * 3;
            } else {
              severity += 1;
            }
            tagCount += tag.count;
        }

        tagArray.sort((a, b) => b.count - a.count);

        obj.severityscore = severity;
        obj.summary = summary;
        obj.weburl = weburl;
        obj.tags = tagArray;

        if (obj.tags && obj.tags.length > 0) {
          const reasonize = (tag) => `${tag.tag} - ${Math.round(tag.count / tagCount * 100)}%`;
          let reason = "Possibility: ";
            let first = true;
            for (let tag of obj.tags) {
                if (first) {
                    reason += reasonize(tag);
                    first = false;
                } else {
                    reason += ", " + reasonize(tag);
                }
            }
            obj.reason = reason;
        }
    }

    /* curl ipinfo.io/98.124.243.43/
{
  "ip": "98.124.243.43",
  "hostname": "No Hostname",
  "city": "Kirkland",
  "region": "Washington",
  "country": "US",
  "loc": "47.6727,-122.1873",
  "org": "AS21740 eNom, Incorporated",
  "postal": "98033"
 */
  async _location(ip) {
    log.info("Looking up location:", ip);

    let cached = await this.cacheLookupAsync(ip, "ipinfo");

    if (cached) {
      return JSON.parse(cached);
    }

    let ipinfo = await Promise.join(
      this._ipInfoFromBone(ip),
      this._ipInfoFromIpinfo(ip),
      (info1, info2) => Object.assign(info1 ? info1 : {}, info2)
    );

    log.info("Merged ipinfo is:", ipinfo);

    this.cacheAdd(ip, "ipinfo", JSON.stringify(ipinfo));

    return ipinfo;
  }

  async _ipInfoFromBone(ip) {
    let result = await bone.intelFinger(ip);
    if (result) {
      log.info("ipInfo from bone is:", result.ipinfo);
      return result.ipinfo;
    }
    log.info("ipInfo from bone is:", null);
    return null;
  }

  async _ipInfoFromIpinfo(ip) {
    const options = {
      uri: "https://ipinfo.io/" + ip,
      method: 'GET',
      family: 4
      // Authorization: 'Token dc30fcd03eddbd95b90bacaea5e5a44b1b60d2f5',
    };

    let body, result;
    try {
      body = await rp(options);
    } catch (err) {
      log.error("Error while requesting", options.uri, err.name, err.cause);
      return;
    }

    try {
      result = JSON.parse(body);
    } catch (err) {
      log.error("Error when parse body:", body, err);
    }

    log.info("ipInfo from ipinfo is:", result);
    return result;
  }

  async _lookup(ip, intel) {
    let url = "https://cymon.io/api/nexus/v1/ip/" + ip + "/events?limit=100";

    let options = {
      uri: url,
      method: 'GET',
      family: 4
    };

    let lobj = await this._location(ip);

    let obj = {lobj};
    log.info("Intel:Location", ip, lobj);
    obj = this._packageIntel(ip, obj, intel);

    let body;
    try {
      body = await rp(options);
    } catch (err) {
      log.info(`Error while requesting ${url}`, err.name, err.cause);

    }

    this.cacheAdd(ip, "cymon", body);

    let cobj = JSON.parse(body);
    if (cobj) {
      if (cobj.count === 0) {
        log.info("INFO:====== No Intel Information!!", ip, obj);
        return obj;
      } else {
        cobj.lobj = lobj;
        this._packageCymon(ip, cobj);
        return cobj;
      }
    } else {
      return obj;
    }
  }
}
<|endoftext|>


<commit_before>(function ( $ ) {
    var PLUGIN_IDENTIFIER = 'mlOverlay';

    function hideOverlay(overlay) {
        var settings = $(overlay).data(PLUGIN_IDENTIFIER);

        if(settings._isVisible) {
            settings.onHide(overlay);
            settings._isVisible = false;
        }

    }

    function showOverlay(overlay) {
        var settings = $(overlay).data(PLUGIN_IDENTIFIER);

        if(!settings._isVisible) {
            settings.onShow(overlay);
            settings._isVisible = true;
        }
    }

    var methods = {
        init : function(options) {
            this.each(function() {
                var _this = this;

                var settings = $.extend({
                    ignore: '',
                    target: '',
                    hideOnOutsideClick: true,
                    hideOnTargetClick: true,
                    hideOnEsc: true,

                    onShow: function(overlay) {
                        $(overlay).fadeIn();
                    },
                    onHide: function(overlay) {
                        $(overlay).fadeOut();
                    },

                    _isVisible : false
                }, options);

                if (settings.ignore.length > 0) {
                    settings.ignore += ",";
                }
                settings.ignore += settings.target;
                $(_this).data(PLUGIN_IDENTIFIER, settings);

                $(settings.target).bind('click.'+PLUGIN_IDENTIFIER, function() {
                    var isOverlayVisible = settings._isVisible;

                    if(isOverlayVisible) {
                        if(settings.hideOnTargetClick) {
                            hideOverlay(_this);
                        }
                    } else {
                        showOverlay(_this);
                    }
                });

                if (settings.hideOnOutsideClick) {
                    $(document).bind('click.'+PLUGIN_IDENTIFIER, function(e) {
                        if ($(e.target).closest(settings.ignore).length == 0 && $(e.target).closest(_this).length == 0) {
                            hideOverlay(_this);
                        }
                    });
                }

                if(settings.hideOnEsc) {
                    $(document).bind('keyup.'+PLUGIN_IDENTIFIER, function(e) {
                        if (e.keyCode == 27) {
                            hideOverlay(_this);
                        }
                    });
                }
            });
        },

        destroy: function() {
            this.each(function() {
                var settings = $(this).data(PLUGIN_IDENTIFIER);
                if(settings) {
                    $(settings.target).unbind('click.'+PLUGIN_IDENTIFIER);
                    $(document).unbind('click.'+PLUGIN_IDENTIFIER);
                    $(document).unbind('keyup.'+PLUGIN_IDENTIFIER);
                    $(this).removeData();
                }
            });
        },
        show : function() {
            this.each(function() {
                showOverlay(this);
            });
        },
        hide : function() {
            this.each(function() {
                hideOverlay(this);
            });
        }
    };

    $.fn.mlOverlay = function(methodOrOptions) {
        if ( methods[methodOrOptions] ) {
            return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
            // prevent re-initialization
            if(!$(this).data(PLUGIN_IDENTIFIER)) {
                return methods.init.apply( this, arguments );
            }
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.'+PLUGIN_IDENTIFIER );
        }
    };
}( jQuery ));<commit_msg>initiate settings only once<commit_after>(function ( $ ) {
    var PLUGIN_IDENTIFIER = 'mlOverlay';

    function hideOverlay(overlay) {
        var settings = $(overlay).data(PLUGIN_IDENTIFIER);

        if(settings._isVisible) {
            settings.onHide(overlay);
            settings._isVisible = false;
        }

    }

    function showOverlay(overlay) {
        var settings = $(overlay).data(PLUGIN_IDENTIFIER);

        if(!settings._isVisible) {
            settings.onShow(overlay);
            settings._isVisible = true;
        }
    }

    var methods = {
        init : function(options) {
            var settings = $.extend({
                ignore: '',
                target: '',
                hideOnOutsideClick: true,
                hideOnTargetClick: true,
                hideOnEsc: true,

                onShow: function(overlay) {
                    $(overlay).fadeIn();
                },
                onHide: function(overlay) {
                    $(overlay).fadeOut();
                },

                _isVisible : false
            }, options);

            if (settings.ignore.length > 0) {
                settings.ignore += ",";
            }
            settings.ignore += settings.target;

            this.each(function() {
                var _this = this;
                $(_this).data(PLUGIN_IDENTIFIER, settings);

                $(settings.target).bind('click.'+PLUGIN_IDENTIFIER, function() {
                    var isOverlayVisible = settings._isVisible;

                    if(isOverlayVisible) {
                        if(settings.hideOnTargetClick) {
                            hideOverlay(_this);
                        }
                    } else {
                        showOverlay(_this);
                    }
                });

                if (settings.hideOnOutsideClick) {
                    $(document).bind('click.'+PLUGIN_IDENTIFIER, function(e) {
                        if ($(e.target).closest(settings.ignore).length == 0 && $(e.target).closest(_this).length == 0) {
                            hideOverlay(_this);
                        }
                    });
                }

                if(settings.hideOnEsc) {
                    $(document).bind('keyup.'+PLUGIN_IDENTIFIER, function(e) {
                        if (e.keyCode == 27) {
                            hideOverlay(_this);
                        }
                    });
                }
            });
        },

        destroy: function() {
            this.each(function() {
                var settings = $(this).data(PLUGIN_IDENTIFIER);
                if(settings) {
                    $(settings.target).unbind('click.'+PLUGIN_IDENTIFIER);
                    $(document).unbind('click.'+PLUGIN_IDENTIFIER);
                    $(document).unbind('keyup.'+PLUGIN_IDENTIFIER);
                    $(this).removeData();
                }
            });
        },
        show : function() {
            this.each(function() {
                showOverlay(this);
            });
        },
        hide : function() {
            this.each(function() {
                hideOverlay(this);
            });
        }
    };

    $.fn.mlOverlay = function(methodOrOptions) {
        if ( methods[methodOrOptions] ) {
            return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
            // prevent re-initialization
            if(!$(this).data(PLUGIN_IDENTIFIER)) {
                return methods.init.apply( this, arguments );
            }
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.'+PLUGIN_IDENTIFIER );
        }
    };
}( jQuery ));<|endoftext|>


<commit_before>/**
 * jquery.multifile.js
 * by Rocky Meza
 *
 * Multifile is a plugin that provides a better interface for
 * uploading more than one file at a time.
 */
;(function($, global, undefined){
  $.fn.multifile = function(attributes)
  {
    var container = attributes['container'];
    var templateCb = attributes['template'];

    var $container
      , addInput = function(event)
        {
          var $this = $(this)
            , new_input = $this.clone(true, false);

          // newer versions of Firefox don't clear this on clone
          new_input[0].value = '';

          $this
            .unbind(event)
            .hide()
            .after(new_input);

          templateCb = templateCb || $.fn.multifile.templateCb;

          templateCb($.fn.multifile.getFileObject(this))
            .appendTo($container)
            .find('.multifile_remove_input')
              .bind('click.multifile', bindRemoveInput($this));
        }
      , bindRemoveInput = function($input)
        {
          // TODO: make this customizable
          return function(event)
          {
            $input.remove();
            $(this).parents('.uploaded_image').remove();

            event.preventDefault();
          };
        };

    if ( container )
    {
      if ( typeof container == 'string' )
        $container = $(container);
      else
        $container = container;
    }
    else
    {
      $container = $('
');
      this.after($container);
    }

    return this.each(function(index, elem)
    {
      $(this)
        .bind('change.multifile', addInput)
        ;
    });
  };

  $.fn.multifile.templateCb = function(file)
  {
    return $(
    '
 \
      x \

')
      .append( $('').attr('class', 'filename').text(file.name) );
  };

  $.fn.multifile.getFileObject = function(input)
  {
    var file = {};
    // check for HTML5 FileList support
    if ( !!global.FileList )
    {
      if ( input.files.length == 1 )
        file = input.files[0];
      else
      {
        file._multiple = true;

        // We do this in order to support `multiple` files.
        // You can't display them separately because they
        // belong to only one file input.  It is impossible
        // to remove just one of the files.
        file.name = input.files[0].name;
        for (var i = 1, _len = input.files.length; i < _len; i++)
          file.name += ', ' + input.files[i].name;
      }
    }
    else
    {
      file._html4 = true;
      file.name = input.value;
    }

    return file;
  };
})(jQuery, this);
<commit_msg>making it possible to pass configuration as object or as two separate parameters<commit_after>/**
 * jquery.multifile.js
 * by Rocky Meza
 *
 * Multifile is a plugin that provides a better interface for
 * uploading more than one file at a time.
 */
;(function($, global, undefined){
  $.fn.multifile = function(containerOrAttributes, templateCb)
  {
    var container;

    if (typeof containerOrAttributes == "object") {
      container = containerOrAttributes['container'];
      templateCb = containerOrAttributes['template'];
    } else {
      container = containerOrAttributes;
    }

    var $container
      , addInput = function(event)
        {
          var $this = $(this)
            , new_input = $this.clone(true, false);

          // newer versions of Firefox don't clear this on clone
          new_input[0].value = '';

          $this
            .unbind(event)
            .hide()
            .after(new_input);

          templateCb = templateCb || $.fn.multifile.templateCb;

          templateCb($.fn.multifile.getFileObject(this))
            .appendTo($container)
            .find('.multifile_remove_input')
              .bind('click.multifile', bindRemoveInput($this));
        }
      , bindRemoveInput = function($input)
        {
          // TODO: make this customizable
          return function(event)
          {
            $input.remove();
            $(this).parents('.uploaded_image').remove();

            event.preventDefault();
          };
        };

    if ( container )
    {
      if ( typeof container == 'string' )
        $container = $(container);
      else
        $container = container;
    }
    else
    {
      $container = $('
');
      this.after($container);
    }

    return this.each(function(index, elem)
    {
      $(this)
        .bind('change.multifile', addInput)
        ;
    });
  };

  $.fn.multifile.templateCb = function(file)
  {
    return $(
    '
 \
      x \

')
      .append( $('').attr('class', 'filename').text(file.name) );
  };

  $.fn.multifile.getFileObject = function(input)
  {
    var file = {};
    // check for HTML5 FileList support
    if ( !!global.FileList )
    {
      if ( input.files.length == 1 )
        file = input.files[0];
      else
      {
        file._multiple = true;

        // We do this in order to support `multiple` files.
        // You can't display them separately because they
        // belong to only one file input.  It is impossible
        // to remove just one of the files.
        file.name = input.files[0].name;
        for (var i = 1, _len = input.files.length; i < _len; i++)
          file.name += ', ' + input.files[i].name;
      }
    }
    else
    {
      file._html4 = true;
      file.name = input.value;
    }

    return file;
  };
})(jQuery, this);
<|endoftext|>


<commit_before>// Utility for creating objects in older browsers
if ( typeof Object.create !== 'function' )
{
  Object.create = function( obj )
  {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

/*!
 * jQuery panelSnap
 * Version 0.7.1
 *
 * Requires:
 * - jQuery 1.7.1 or higher (Works with the API changes from 1.9.1 too)
 * - jQuery scrollEvents.js (included in the package)
 *
 * https://github.com/guidobouman/jquery-panelsnap
 *
 * Copyright 2013, Guido Bouman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Date: Wed Feb 13 16:05:00 2013 +0100
 */
(function($, window, document, undefined)
{
  var pluginName = 'panelSnap';
  var storageName = 'plugin_' + pluginName;

  var pluginObject = {
    isMouseDown: false,
    isSnapping: false,
    scrollInterval: 0,
    scrollOffset: 0,

    init: function(options, container)
    {
      var self = this;

      self.$window = $(window);
      self.$document = $(document);

      self.container = container;
      self.$container = $(container);

      self.$eventContainer = self.$container;
      if(self.$container.is('body'))
      {
        self.$eventContainer = self.$document;
      }

      self.options = $.extend(true, {}, $.fn.panelSnap.options, options);

      self.bind();

      if(self.options.$menu !== false && $('.active', self.options.$menu).length > 0)
      {
        $('.active', self.options.$menu).click();
      }
      else
      {
        self.activatePanel(self.$container.children(':first'));
      }

      return self;
    },

    bind: function()
    {
      var self = this;

      self.bindProxied(self.$eventContainer, 'scrollstart', self.scrollStart);
      self.bindProxied(self.$eventContainer, 'scrollstop', self.scrollStop);
      self.bindProxied(self.$eventContainer, 'mousewheel', self.mouseWheel);
      self.bindProxied(self.$eventContainer, 'mousedown', self.mouseDown);
      self.bindProxied(self.$eventContainer, 'mouseup', self.mouseUp);

      self.bindProxied(self.$window, 'resize', self.scrollStop);

      if(self.options.$menu !== false)
      {
        self.bindProxied($(self.options.$menu), 'click', self.captureMenuClick, self.options.menuSelector);
      }
    },

    bindProxied: function($element, event, method, selector)
    {
      var self = this;

      selector = typeof selector === 'string' ? selector : null;

      $element.on(event + self.options.namespace, selector, $.proxy(function(e)
      {
        return method.call(self, e);
      }, self));
    },

    destroy: function()
    {
      var self = this;

      // Gotta love namespaced events!
      self.$eventContainer.off(self.options.namespace);

      self.$window.off(self.options.namespace);

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector, self.options.$menu).off(self.options.namespace);
      }

      self.$container.removeData(storageName);
    },

    captureMenuClick: function(e)
    {
      var self = this;
      var panel = $(e.currentTarget).data('panel');
      var selector = self.options.panelSelector + '[data-panel=' + panel + ']';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);

      return false;
    },

    scrollStart: function(e)
    {
      var self = this;

      // Deprecated, moved storage of scrollOffset to callback of panelSnap
      // self.scrollOffset = self.$container.scrollTop();
    },

    scrollStop: function(e)
    {
      var self = this;

      if(self.isMouseDown)
      {
        self.$eventContainer.one('mouseup', self.processScroll);
        return;
      }

      if(self.isSnapping)
      {
        return;
      }

      var interval = self.$container.height();
      var intervalDifference = interval - self.scrollInterval;
      var offset = self.$container.scrollTop();
      var scrollDifference = offset - self.scrollOffset;
      var maxOffset = self.$container[0].scrollHeight - interval;

      self.scrollInterval = interval;

      if(
        scrollDifference === 0  && intervalDifference === 0 ||
        offset < 0 ||
        offset > maxOffset
      )
      {
        return;
      }

      var child_number;
      if(scrollDifference < -self.options.directionThreshold && scrollDifference > -interval)
      {
        child_number = Math.floor(offset / interval);
      }
      else if(scrollDifference > self.options.directionThreshold && scrollDifference < interval)
      {
        child_number = Math.ceil(offset / interval);
      }
      else
      {
        child_number = Math.round(offset / interval);
      }

      child_number += 1;

      var selector = '> ' + self.options.panelSelector + ':nth-child(' + child_number + ')';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);
    },

    mouseWheel: function(e)
    {
      var self = this;

      // This event only fires when the user actually scrolls with their input device.
      // Be it a trackpad, legacy mouse or anything else.

      self.$container.stop(true);
      self.isSnapping = false;
    },

    mouseDown: function(e)
    {
      var self = this;

      self.isMouseDown = true;
    },

    mouseUp: function(e)
    {
      var self = this;

      self.isMouseDown = false;
    },

    snapToPanel: function($target)
    {
      var self = this;

      self.isSnapping = true;

      self.options.onSnapStart.call(self, $target);
      self.$container.trigger('panelsnap:start', [$target]);

      var scrollTarget = 0;
      if(self.$container.is('body'))
      {
        scrollTarget = $target.offset().top;
      }
      else
      {
        scrollTarget = self.$container.scrollTop() + $target.position().top;
      }

      self.$container.animate(
      {
        scrollTop: scrollTarget
      }, self.options.slideSpeed, function()
      {
        self.scrollOffset = scrollTarget;
        self.isSnapping = false;

        // Call callback
        self.options.onSnapFinish.call(self, $target);
        self.$container.trigger('panelsnap:finish', [$target]);
      });

      self.activatePanel($target);
    },

    activatePanel: function($target)
    {
      var self = this;

      $('> ' + self.options.panelSelector + '.active', self.container).removeClass('active');
      $target.addClass('active');

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector + '.active', self.options.$menu).removeClass('active');
        var itemSelector = self.options.menuSelector + '[data-panel=' + $target.data('panel') + ']';
        var $activeItem = $(itemSelector, self.options.$menu);
        $activeItem.addClass('active');
      }
    }
  };

  $.fn[pluginName] = function(options)
  {
    return this.each(function()
    {
      var pluginInstance = $.data(this, storageName);
      if(typeof options === 'object' || options === 'init' || ! options)
      {
        if(!pluginInstance)
        {
          if(options === 'init')
          {
            options = Array.prototype.slice.call(arguments, 1);
          }

          pluginInstance = $.data(this, storageName, Object.create(pluginObject).init(options, this));
        }
        else
        {
          $.error('Plugin is already initialized for this object.');
          return;
        }
      }
      else if(!pluginInstance)
      {
        $.error('Plugin is not initialized for this object yet.');
        return;
      }
      else if(pluginInstance[options])
      {
        method = options;
        options = Array.prototype.slice.call(arguments, 1);
        pluginInstance[method](options);
      }
      else
      {
        $.error('Method ' +  options + ' does not exist on jQuery.panelSnap.');
        return;
      }
    });
  };

  $.fn.panelSnap.options = {
    $menu: false,
    menuSelector: 'a',
    panelSelector: 'section',
    namespace: '.panelSnap',
    onSnapStart: function(){},
    onSnapFinish: function(){},
    directionThreshold: 50,
    slideSpeed: 200
  };

})(jQuery, window, document);
<commit_msg>Adds namespace to last event, making sure it will unbind on destroy.<commit_after>// Utility for creating objects in older browsers
if ( typeof Object.create !== 'function' )
{
  Object.create = function( obj )
  {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

/*!
 * jQuery panelSnap
 * Version 0.7.1
 *
 * Requires:
 * - jQuery 1.7.1 or higher (Works with the API changes from 1.9.1 too)
 * - jQuery scrollEvents.js (included in the package)
 *
 * https://github.com/guidobouman/jquery-panelsnap
 *
 * Copyright 2013, Guido Bouman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Date: Wed Feb 13 16:05:00 2013 +0100
 */
(function($, window, document, undefined)
{
  var pluginName = 'panelSnap';
  var storageName = 'plugin_' + pluginName;

  var pluginObject = {
    isMouseDown: false,
    isSnapping: false,
    scrollInterval: 0,
    scrollOffset: 0,

    init: function(options, container)
    {
      var self = this;

      self.$window = $(window);
      self.$document = $(document);

      self.container = container;
      self.$container = $(container);

      self.$eventContainer = self.$container;
      if(self.$container.is('body'))
      {
        self.$eventContainer = self.$document;
      }

      self.options = $.extend(true, {}, $.fn.panelSnap.options, options);

      self.bind();

      if(self.options.$menu !== false && $('.active', self.options.$menu).length > 0)
      {
        $('.active', self.options.$menu).click();
      }
      else
      {
        self.activatePanel(self.$container.children(':first'));
      }

      return self;
    },

    bind: function()
    {
      var self = this;

      self.bindProxied(self.$eventContainer, 'scrollstart', self.scrollStart);
      self.bindProxied(self.$eventContainer, 'scrollstop', self.scrollStop);
      self.bindProxied(self.$eventContainer, 'mousewheel', self.mouseWheel);
      self.bindProxied(self.$eventContainer, 'mousedown', self.mouseDown);
      self.bindProxied(self.$eventContainer, 'mouseup', self.mouseUp);

      self.bindProxied(self.$window, 'resize', self.scrollStop);

      if(self.options.$menu !== false)
      {
        self.bindProxied($(self.options.$menu), 'click', self.captureMenuClick, self.options.menuSelector);
      }
    },

    bindProxied: function($element, event, method, selector)
    {
      var self = this;

      selector = typeof selector === 'string' ? selector : null;

      $element.on(event + self.options.namespace, selector, $.proxy(function(e)
      {
        return method.call(self, e);
      }, self));
    },

    destroy: function()
    {
      var self = this;

      // Gotta love namespaced events!
      self.$eventContainer.off(self.options.namespace);

      self.$window.off(self.options.namespace);

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector, self.options.$menu).off(self.options.namespace);
      }

      self.$container.removeData(storageName);
    },

    captureMenuClick: function(e)
    {
      var self = this;
      var panel = $(e.currentTarget).data('panel');
      var selector = self.options.panelSelector + '[data-panel=' + panel + ']';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);

      return false;
    },

    scrollStart: function(e)
    {
      var self = this;

      // Deprecated, moved storage of scrollOffset to callback of panelSnap
      // self.scrollOffset = self.$container.scrollTop();
    },

    scrollStop: function(e)
    {
      var self = this;

      if(self.isMouseDown)
      {
        self.$eventContainer.one('mouseup' + self.options.namespace, self.processScroll);
        return;
      }

      if(self.isSnapping)
      {
        return;
      }

      var interval = self.$container.height();
      var intervalDifference = interval - self.scrollInterval;
      var offset = self.$container.scrollTop();
      var scrollDifference = offset - self.scrollOffset;
      var maxOffset = self.$container[0].scrollHeight - interval;

      self.scrollInterval = interval;

      if(
        scrollDifference === 0  && intervalDifference === 0 ||
        offset < 0 ||
        offset > maxOffset
      )
      {
        return;
      }

      var child_number;
      if(scrollDifference < -self.options.directionThreshold && scrollDifference > -interval)
      {
        child_number = Math.floor(offset / interval);
      }
      else if(scrollDifference > self.options.directionThreshold && scrollDifference < interval)
      {
        child_number = Math.ceil(offset / interval);
      }
      else
      {
        child_number = Math.round(offset / interval);
      }

      child_number += 1;

      var selector = '> ' + self.options.panelSelector + ':nth-child(' + child_number + ')';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);
    },

    mouseWheel: function(e)
    {
      var self = this;

      // This event only fires when the user actually scrolls with their input device.
      // Be it a trackpad, legacy mouse or anything else.

      self.$container.stop(true);
      self.isSnapping = false;
    },

    mouseDown: function(e)
    {
      var self = this;

      self.isMouseDown = true;
    },

    mouseUp: function(e)
    {
      var self = this;

      self.isMouseDown = false;
    },

    snapToPanel: function($target)
    {
      var self = this;

      self.isSnapping = true;

      self.options.onSnapStart.call(self, $target);
      self.$container.trigger('panelsnap:start', [$target]);

      var scrollTarget = 0;
      if(self.$container.is('body'))
      {
        scrollTarget = $target.offset().top;
      }
      else
      {
        scrollTarget = self.$container.scrollTop() + $target.position().top;
      }

      self.$container.animate(
      {
        scrollTop: scrollTarget
      }, self.options.slideSpeed, function()
      {
        self.scrollOffset = scrollTarget;
        self.isSnapping = false;

        // Call callback
        self.options.onSnapFinish.call(self, $target);
        self.$container.trigger('panelsnap:finish', [$target]);
      });

      self.activatePanel($target);
    },

    activatePanel: function($target)
    {
      var self = this;

      $('> ' + self.options.panelSelector + '.active', self.container).removeClass('active');
      $target.addClass('active');

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector + '.active', self.options.$menu).removeClass('active');
        var itemSelector = self.options.menuSelector + '[data-panel=' + $target.data('panel') + ']';
        var $activeItem = $(itemSelector, self.options.$menu);
        $activeItem.addClass('active');
      }
    }
  };

  $.fn[pluginName] = function(options)
  {
    return this.each(function()
    {
      var pluginInstance = $.data(this, storageName);
      if(typeof options === 'object' || options === 'init' || ! options)
      {
        if(!pluginInstance)
        {
          if(options === 'init')
          {
            options = Array.prototype.slice.call(arguments, 1);
          }

          pluginInstance = $.data(this, storageName, Object.create(pluginObject).init(options, this));
        }
        else
        {
          $.error('Plugin is already initialized for this object.');
          return;
        }
      }
      else if(!pluginInstance)
      {
        $.error('Plugin is not initialized for this object yet.');
        return;
      }
      else if(pluginInstance[options])
      {
        method = options;
        options = Array.prototype.slice.call(arguments, 1);
        pluginInstance[method](options);
      }
      else
      {
        $.error('Method ' +  options + ' does not exist on jQuery.panelSnap.');
        return;
      }
    });
  };

  $.fn.panelSnap.options = {
    $menu: false,
    menuSelector: 'a',
    panelSelector: 'section',
    namespace: '.panelSnap',
    onSnapStart: function(){},
    onSnapFinish: function(){},
    directionThreshold: 50,
    slideSpeed: 200
  };

})(jQuery, window, document);
<|endoftext|>


<commit_before>// Utility for creating objects in older browsers
if ( typeof Object.create !== 'function' )
{
  Object.create = function( obj )
  {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

/*!
 * jQuery panelSnap
 * Version 0.7.3
 *
 * Requires:
 * - jQuery 1.7.1 or higher (Works with the API changes from 1.9.1 too)
 * - jQuery scrollEvents.js (included in the package)
 *
 * https://github.com/guidobouman/jquery-panelsnap
 *
 * Copyright 2013, Guido Bouman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Date: Wed Feb 13 16:05:00 2013 +0100
 */
(function($, window, document, undefined)
{
  var pluginName = 'panelSnap';
  var storageName = 'plugin_' + pluginName;

  var pluginObject = {
    isMouseDown: false,
    isSnapping: false,
    scrollInterval: 0,
    scrollOffset: 0,

    init: function(options, container)
    {
      var self = this;

      self.$window = $(window);
      self.$document = $(document);

      self.container = container;
      self.$container = $(container);

      self.$eventContainer = self.$container;
      if(self.$container.is('body'))
      {
        self.$eventContainer = self.$document;
      }

      self.options = $.extend(true, {}, $.fn.panelSnap.options, options);

      self.bind();

      if(self.options.$menu !== false && $('.active', self.options.$menu).length > 0)
      {
        $('.active', self.options.$menu).click();
      }
      else
      {
        self.activatePanel(self.$container.children(':first'));
      }

      return self;
    },

    bind: function()
    {
      var self = this;

      self.bindProxied(self.$eventContainer, 'scrollstart', self.scrollStart);
      self.bindProxied(self.$eventContainer, 'scrollstop', self.scrollStop);
      self.bindProxied(self.$eventContainer, 'mousewheel', self.mouseWheel);
      self.bindProxied(self.$eventContainer, 'mousedown', self.mouseDown);
      self.bindProxied(self.$eventContainer, 'mouseup', self.mouseUp);

      self.bindProxied(self.$window, 'resize', self.scrollStop);

      if(self.options.$menu !== false)
      {
        self.bindProxied($(self.options.$menu), 'click', self.captureMenuClick, self.options.menuSelector);
      }
    },

    bindProxied: function($element, event, method, selector)
    {
      var self = this;

      selector = typeof selector === 'string' ? selector : null;

      $element.on(event + self.options.namespace, selector, $.proxy(function(e)
      {
        return method.call(self, e);
      }, self));
    },

    destroy: function()
    {
      var self = this;

      // Gotta love namespaced events!
      self.$eventContainer.off(self.options.namespace);

      self.$window.off(self.options.namespace);

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector, self.options.$menu).off(self.options.namespace);
      }

      self.$container.removeData(storageName);
    },

    captureMenuClick: function(e)
    {
      var self = this;
      var panel = $(e.currentTarget).data('panel');
      var selector = self.options.panelSelector + '[data-panel=' + panel + ']';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);

      return false;
    },

    scrollStart: function(e)
    {
      var self = this;

      // Deprecated, moved storage of scrollOffset to callback of panelSnap
      // self.scrollOffset = self.$container.scrollTop();
    },

    scrollStop: function(e)
    {
      var self = this;

      if(self.isMouseDown)
      {
        self.$eventContainer.one('mouseup' + self.options.namespace, self.processScroll);
        return;
      }

      if(self.isSnapping)
      {
        return;
      }

      var interval = self.$container.height();
      var intervalDifference = interval - self.scrollInterval;
      var offset = self.$container.scrollTop();
      var scrollDifference = offset - self.scrollOffset;
      var maxOffset = self.$container[0].scrollHeight - interval;

      self.scrollInterval = interval;

      if(
        scrollDifference === 0  && intervalDifference === 0 ||
        offset < 0 ||
        offset > maxOffset
      )
      {
        return;
      }

      var child_number;
      if(scrollDifference < -self.options.directionThreshold && scrollDifference > -interval)
      {
        child_number = Math.floor(offset / interval);
      }
      else if(scrollDifference > self.options.directionThreshold && scrollDifference < interval)
      {
        child_number = Math.ceil(offset / interval);
      }
      else
      {
        child_number = Math.round(offset / interval);
      }

      var selector = '> ' + self.options.panelSelector + ':eq(' + child_number + ')';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);
    },

    mouseWheel: function(e)
    {
      var self = this;

      // This event only fires when the user actually scrolls with their input device.
      // Be it a trackpad, legacy mouse or anything else.

      self.$container.stop(true);
      self.isSnapping = false;
    },

    mouseDown: function(e)
    {
      var self = this;

      self.isMouseDown = true;
    },

    mouseUp: function(e)
    {
      var self = this;

      self.isMouseDown = false;
    },

    snapToPanel: function($target)
    {
      var self = this;

      self.isSnapping = true;

      self.options.onSnapStart.call(self, $target);
      self.$container.trigger('panelsnap:start', [$target]);

      var scrollTarget = 0;
      if(self.$container.is('body'))
      {
        scrollTarget = $target.offset().top;
      }
      else
      {
        scrollTarget = self.$container.scrollTop() + $target.position().top;
      }

      self.$container.animate(
      {
        scrollTop: scrollTarget
      }, self.options.slideSpeed, function()
      {
        self.scrollOffset = scrollTarget;
        self.isSnapping = false;

        // Call callback
        self.options.onSnapFinish.call(self, $target);
        self.$container.trigger('panelsnap:finish', [$target]);
      });

      self.activatePanel($target);
    },

    activatePanel: function($target)
    {
      var self = this;

      $('> ' + self.options.panelSelector + '.active', self.container).removeClass('active');
      $target.addClass('active');

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector + '.active', self.options.$menu).removeClass('active');
        var itemSelector = self.options.menuSelector + '[data-panel=' + $target.data('panel') + ']';
        var $activeItem = $(itemSelector, self.options.$menu);
        $activeItem.addClass('active');
      }
    }
  };

  $.fn[pluginName] = function(options)
  {
    return this.each(function()
    {
      var pluginInstance = $.data(this, storageName);
      if(typeof options === 'object' || options === 'init' || ! options)
      {
        if(!pluginInstance)
        {
          if(options === 'init')
          {
            options = Array.prototype.slice.call(arguments, 1);
          }

          pluginInstance = $.data(this, storageName, Object.create(pluginObject).init(options, this));
        }
        else
        {
          $.error('Plugin is already initialized for this object.');
          return;
        }
      }
      else if(!pluginInstance)
      {
        $.error('Plugin is not initialized for this object yet.');
        return;
      }
      else if(pluginInstance[options])
      {
        method = options;
        options = Array.prototype.slice.call(arguments, 1);
        pluginInstance[method](options);
      }
      else
      {
        $.error('Method ' +  options + ' does not exist on jQuery.panelSnap.');
        return;
      }
    });
  };

  $.fn.panelSnap.options = {
    $menu: false,
    menuSelector: 'a',
    panelSelector: 'section',
    namespace: '.panelSnap',
    onSnapStart: function(){},
    onSnapFinish: function(){},
    directionThreshold: 50,
    slideSpeed: 200
  };

})(jQuery, window, document);
<commit_msg>Prevents stacking of snapping animations.<commit_after>// Utility for creating objects in older browsers
if ( typeof Object.create !== 'function' )
{
  Object.create = function( obj )
  {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

/*!
 * jQuery panelSnap
 * Version 0.7.3
 *
 * Requires:
 * - jQuery 1.7.1 or higher (Works with the API changes from 1.9.1 too)
 * - jQuery scrollEvents.js (included in the package)
 *
 * https://github.com/guidobouman/jquery-panelsnap
 *
 * Copyright 2013, Guido Bouman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Date: Wed Feb 13 16:05:00 2013 +0100
 */
(function($, window, document, undefined)
{
  var pluginName = 'panelSnap';
  var storageName = 'plugin_' + pluginName;

  var pluginObject = {
    isMouseDown: false,
    isSnapping: false,
    scrollInterval: 0,
    scrollOffset: 0,

    init: function(options, container)
    {
      var self = this;

      self.$window = $(window);
      self.$document = $(document);

      self.container = container;
      self.$container = $(container);

      self.$eventContainer = self.$container;
      if(self.$container.is('body'))
      {
        self.$eventContainer = self.$document;
      }

      self.options = $.extend(true, {}, $.fn.panelSnap.options, options);

      self.bind();

      if(self.options.$menu !== false && $('.active', self.options.$menu).length > 0)
      {
        $('.active', self.options.$menu).click();
      }
      else
      {
        self.activatePanel(self.$container.children(':first'));
      }

      return self;
    },

    bind: function()
    {
      var self = this;

      self.bindProxied(self.$eventContainer, 'scrollstart', self.scrollStart);
      self.bindProxied(self.$eventContainer, 'scrollstop', self.scrollStop);
      self.bindProxied(self.$eventContainer, 'mousewheel', self.mouseWheel);
      self.bindProxied(self.$eventContainer, 'mousedown', self.mouseDown);
      self.bindProxied(self.$eventContainer, 'mouseup', self.mouseUp);

      self.bindProxied(self.$window, 'resize', self.scrollStop);

      if(self.options.$menu !== false)
      {
        self.bindProxied($(self.options.$menu), 'click', self.captureMenuClick, self.options.menuSelector);
      }
    },

    bindProxied: function($element, event, method, selector)
    {
      var self = this;

      selector = typeof selector === 'string' ? selector : null;

      $element.on(event + self.options.namespace, selector, $.proxy(function(e)
      {
        return method.call(self, e);
      }, self));
    },

    destroy: function()
    {
      var self = this;

      // Gotta love namespaced events!
      self.$eventContainer.off(self.options.namespace);

      self.$window.off(self.options.namespace);

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector, self.options.$menu).off(self.options.namespace);
      }

      self.$container.removeData(storageName);
    },

    captureMenuClick: function(e)
    {
      var self = this;
      var panel = $(e.currentTarget).data('panel');
      var selector = self.options.panelSelector + '[data-panel=' + panel + ']';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);

      return false;
    },

    scrollStart: function(e)
    {
      var self = this;

      // Deprecated, moved storage of scrollOffset to callback of panelSnap
      // self.scrollOffset = self.$container.scrollTop();
    },

    scrollStop: function(e)
    {
      var self = this;

      if(self.isMouseDown)
      {
        self.$eventContainer.one('mouseup' + self.options.namespace, self.processScroll);
        return;
      }

      if(self.isSnapping)
      {
        return;
      }

      var interval = self.$container.height();
      var intervalDifference = interval - self.scrollInterval;
      var offset = self.$container.scrollTop();
      var scrollDifference = offset - self.scrollOffset;
      var maxOffset = self.$container[0].scrollHeight - interval;

      self.scrollInterval = interval;

      if(
        scrollDifference === 0  && intervalDifference === 0 ||
        offset < 0 ||
        offset > maxOffset
      )
      {
        return;
      }

      var child_number;
      if(scrollDifference < -self.options.directionThreshold && scrollDifference > -interval)
      {
        child_number = Math.floor(offset / interval);
      }
      else if(scrollDifference > self.options.directionThreshold && scrollDifference < interval)
      {
        child_number = Math.ceil(offset / interval);
      }
      else
      {
        child_number = Math.round(offset / interval);
      }

      var selector = '> ' + self.options.panelSelector + ':eq(' + child_number + ')';
      var $target = $(selector, self.$container);

      self.snapToPanel($target);
    },

    mouseWheel: function(e)
    {
      var self = this;

      // This event only fires when the user actually scrolls with their input device.
      // Be it a trackpad, legacy mouse or anything else.

      self.$container.stop(true);
      self.isSnapping = false;
    },

    mouseDown: function(e)
    {
      var self = this;

      self.isMouseDown = true;
    },

    mouseUp: function(e)
    {
      var self = this;

      self.isMouseDown = false;
    },

    snapToPanel: function($target)
    {
      var self = this;

      self.isSnapping = true;

      self.options.onSnapStart.call(self, $target);
      self.$container.trigger('panelsnap:start', [$target]);

      var scrollTarget = 0;
      if(self.$container.is('body'))
      {
        scrollTarget = $target.offset().top;
      }
      else
      {
        scrollTarget = self.$container.scrollTop() + $target.position().top;
      }

      self.$container.stop(true).animate(
      {
        scrollTop: scrollTarget
      }, self.options.slideSpeed, function()
      {
        self.scrollOffset = scrollTarget;
        self.isSnapping = false;

        // Call callback
        self.options.onSnapFinish.call(self, $target);
        self.$container.trigger('panelsnap:finish', [$target]);
      });

      self.activatePanel($target);
    },

    activatePanel: function($target)
    {
      var self = this;

      $('> ' + self.options.panelSelector + '.active', self.container).removeClass('active');
      $target.addClass('active');

      if(self.options.$menu !== false)
      {
        $(self.options.menuSelector + '.active', self.options.$menu).removeClass('active');
        var itemSelector = self.options.menuSelector + '[data-panel=' + $target.data('panel') + ']';
        var $activeItem = $(itemSelector, self.options.$menu);
        $activeItem.addClass('active');
      }
    }
  };

  $.fn[pluginName] = function(options)
  {
    return this.each(function()
    {
      var pluginInstance = $.data(this, storageName);
      if(typeof options === 'object' || options === 'init' || ! options)
      {
        if(!pluginInstance)
        {
          if(options === 'init')
          {
            options = Array.prototype.slice.call(arguments, 1);
          }

          pluginInstance = $.data(this, storageName, Object.create(pluginObject).init(options, this));
        }
        else
        {
          $.error('Plugin is already initialized for this object.');
          return;
        }
      }
      else if(!pluginInstance)
      {
        $.error('Plugin is not initialized for this object yet.');
        return;
      }
      else if(pluginInstance[options])
      {
        method = options;
        options = Array.prototype.slice.call(arguments, 1);
        pluginInstance[method](options);
      }
      else
      {
        $.error('Method ' +  options + ' does not exist on jQuery.panelSnap.');
        return;
      }
    });
  };

  $.fn.panelSnap.options = {
    $menu: false,
    menuSelector: 'a',
    panelSelector: 'section',
    namespace: '.panelSnap',
    onSnapStart: function(){},
    onSnapFinish: function(){},
    directionThreshold: 50,
    slideSpeed: 200
  };

})(jQuery, window, document);
<|endoftext|>


<commit_before>import DS from 'ember-data';

let Prompt = DS.Model.extend({
  step: DS.belongsTo('step'),

  question: DS.attr('string'), //our prompt
  answer: DS.attr('string'), // their answer
  clause: DS.attr('string')
});

let data = [
	{step: 1, prompts: ['My business is called %businessName'] },
	{step: 2, prompts: ['How will the weather be tomorrow? %weather'] },
	{step: 3, prompts: ['Do you use google adsense? %goodle-adsense', 'Do you use Amazon Web Services? %amazon'] },
	{step: 4, prompts: ['Where do you store the data collected? %data-collected']}
]

//Turn the data above into the format that ember fixtures wants
var fixtures = [];
var id_number = 0;

for (let step of data) {
	id_number +=1;
	let step_number = step.step;
	let prompts = step.prompts;
	for (let question of prompts) {
		fixtures.push({id: id_number, question: question, step: step_number});
	}
}

console.log(fixtures);

Prompt.reopenClass({
	FIXTURES: fixtures
});

export default Prompt;
<commit_msg>added missing semicolon<commit_after>import DS from 'ember-data';

let Prompt = DS.Model.extend({
  step: DS.belongsTo('step'),

  question: DS.attr('string'), //our prompt
  answer: DS.attr('string'), // their answer
  clause: DS.attr('string')
});

let data = [
	{step: 1, prompts: ['My business is called %businessName'] },
	{step: 2, prompts: ['How will the weather be tomorrow? %weather'] },
	{step: 3, prompts: ['Do you use google adsense? %goodle-adsense', 'Do you use Amazon Web Services? %amazon'] },
	{step: 4, prompts: ['Where do you store the data collected? %data-collected']}
	];

//Turn the data above into the format that ember fixtures wants
var fixtures = [];
var id_number = 0;

for (let step of data) {
	id_number +=1;
	let step_number = step.step;
	let prompts = step.prompts;
	for (let question of prompts) {
		fixtures.push({id: id_number, question: question, step: step_number});
	}
}

console.log(fixtures);

Prompt.reopenClass({
	FIXTURES: fixtures
});

export default Prompt;
<|endoftext|>


<commit_before>/*

	jQuery Tags Input Plugin 1.2.3

	Copyright (c) 2010 XOXCO, Inc

	Documentation for this plugin lives here:
	http://xoxco.com/clickable/jquery-tags-input

	Licensed under the MIT license:
	http://www.opensource.org/licenses/mit-license.php

	ben@xoxco.com

*/

(function($) {

	var delimiter = new Array();
	var tags_callbacks = new Array();

	jQuery.fn.addTag = function(value,options) {

			var options = jQuery.extend({focus:false},options);
			var options = jQuery.extend({focus:false,callback:true},options);
			this.each(function() {
				id = $(this).attr('id');

				var tagslist = $(this).val().split(delimiter[id]);
				if (tagslist[0] == '') {
					tagslist = new Array();
				}

				value = jQuery.trim(value);

				if (options.unique) {
					skipTag = $(tagslist).tagExist(value);
				} else {
					skipTag = false;
				}

				if (value !='' && skipTag != true) {

					$(''+value + '  x').insertBefore('#'+id+'_addTag');
					tagslist.push(value);

					$('#'+id+'_tag').val('');
					if (options.focus) {
						$('#'+id+'_tag').focus();
					} else {
						$('#'+id+'_tag').blur();
					}

					if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
						var f = tags_callbacks[id]['onAddTag'];
						f(value);
					}
				}
				jQuery.fn.tagsInput.updateTagsField(this,tagslist);

			});

			return false;
		};

	jQuery.fn.removeTag = function(value) {

			this.each(function() {
				id = $(this).attr('id');

				var old = $(this).val().split(delimiter[id]);

				$('#'+id+'_tagsinput .tag').remove();
				str = '';
				for (i=0; i< old.length; i++) {
					if (escape(old[i])!=value) {
						str = str + delimiter[id] +old[i];
					}
				}

				jQuery.fn.tagsInput.importTags(this,str);

				if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
					var f = tags_callbacks[id]['onRemoveTag'];
					f(value);
				}
			});

			return false;

		};

	jQuery.fn.tagExist = function(val) {

		if (jQuery.inArray(val, $(this)) == -1) {
		  return false; /* Cannot find value in array */
		} else {
		  return true; /* Value found */
		}
	};

	// clear all existing tags and import new ones from a string
	jQuery.fn.importTags = function(str) {
		$('#'+id+'_tagsinput .tag').remove();
		jQuery.fn.tagsInput.importTags(this,str);
	}

	jQuery.fn.tagsInput = function(options) {

		var settings = jQuery.extend({defaultText:'add a tag',minChars:0,width:'300px',height:'100px','hide':true,'delimiter':',',autocomplete:{selectFirst:false},'unique':true},options);

		this.each(function() {
			if (settings.hide) {
				$(this).hide();
			}

			id = $(this).attr('id')

			data = jQuery.extend({
				pid:id,
				real_input: '#'+id,
				holder: '#'+id+'_tagsinput',
				input_wrapper: '#'+id+'_addTag',
				fake_input: '#'+id+'_tag'
			},settings);

			delimiter[id] = data.delimiter;

			$('

<input id="'+id+'_tag" value="" default="'+settings.defaultText+'" />

').insertAfter(this);

			$(data.holder).css('width',settings.width);
			$(data.holder).css('height',settings.height);

			if ($(data.real_input).val()!='') {
				jQuery.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
			} else {
				$(data.fake_input).val($(data.fake_input).attr('default'));
				$(data.fake_input).css('color','#666666');
			}

			$(data.holder).bind('click',data,function(event) {
				$(event.data.fake_input).focus();
			});

			$(data.fake_input).bind('focus',data,function(event) {
				if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('default')) {
					$(event.data.fake_input).val('');
				}
				$(event.data.fake_input).css('color','#000000');
			});

			if (settings.autocomplete_url != undefined) {
				$(data.fake_input).autocomplete(settings.autocomplete_url,settings.autocomplete).bind('result',data,function(event,data,formatted) {
					if (data)
					{
						$(event.data.real_input).addTag(formatted,{focus:true,unique:(settings.unique)});
					}
				});

				$(data.fake_input).bind('blur',data,function(event) {
					if( $('.ac_results').is(':visible') ) return false;
					if ( $(event.data.fake_input).val() != $(event.data.fake_input).attr('default')) {
						if((event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)))
							$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:false,unique:(settings.unique)});
					}

					$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
					$(event.data.fake_input).css('color','#666666');
					return false;
				});

			} else {
					// if a user tabs out of the field, create a new tag
					// this is only available if autocomplete is not used.
					$(data.fake_input).bind('blur',data,function(event) {
						var d = $(this).attr('default');
						if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
							if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
								$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
						} else {
							$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
							$(event.data.fake_input).css('color','#666666');
						}
						return false;
					});

			}
			// if user types a comma, create a new tag
			$(data.fake_input).bind('keypress',data,function(event) {
				if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) {
					if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
						$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});

					return false;
				}
			});
			$(data.fake_input).blur();
			return false;
		});

		return this;

	};

	jQuery.fn.tagsInput.updateTagsField = function(obj,tagslist) {

			id = $(obj).attr('id');
			$(obj).val(tagslist.join(delimiter[id]));
		};

	jQuery.fn.tagsInput.importTags = function(obj,val) {

			$(obj).val('');
			id = $(obj).attr('id');
			var tags = val.split(delimiter[id]);
			for (i=0; i<tags.length; i++) {
				$(obj).addTag(tags[i],{focus:false});
			}
		};

})(jQuery);
<commit_msg>Store the callback functions for later.<commit_after>/*

	jQuery Tags Input Plugin 1.2.3

	Copyright (c) 2010 XOXCO, Inc

	Documentation for this plugin lives here:
	http://xoxco.com/clickable/jquery-tags-input

	Licensed under the MIT license:
	http://www.opensource.org/licenses/mit-license.php

	ben@xoxco.com

*/

(function($) {

	var delimiter = new Array();
	var tags_callbacks = new Array();

	jQuery.fn.addTag = function(value,options) {

			var options = jQuery.extend({focus:false},options);
			var options = jQuery.extend({focus:false,callback:true},options);
			this.each(function() {
				id = $(this).attr('id');

				var tagslist = $(this).val().split(delimiter[id]);
				if (tagslist[0] == '') {
					tagslist = new Array();
				}

				value = jQuery.trim(value);

				if (options.unique) {
					skipTag = $(tagslist).tagExist(value);
				} else {
					skipTag = false;
				}

				if (value !='' && skipTag != true) {

					$(''+value + '  x').insertBefore('#'+id+'_addTag');
					tagslist.push(value);

					$('#'+id+'_tag').val('');
					if (options.focus) {
						$('#'+id+'_tag').focus();
					} else {
						$('#'+id+'_tag').blur();
					}

					if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
						var f = tags_callbacks[id]['onAddTag'];
						f(value);
					}
				}
				jQuery.fn.tagsInput.updateTagsField(this,tagslist);

			});

			return false;
		};

	jQuery.fn.removeTag = function(value) {

			this.each(function() {
				id = $(this).attr('id');

				var old = $(this).val().split(delimiter[id]);

				$('#'+id+'_tagsinput .tag').remove();
				str = '';
				for (i=0; i< old.length; i++) {
					if (escape(old[i])!=value) {
						str = str + delimiter[id] +old[i];
					}
				}

				jQuery.fn.tagsInput.importTags(this,str);

				if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
					var f = tags_callbacks[id]['onRemoveTag'];
					f(value);
				}
			});

			return false;

		};

	jQuery.fn.tagExist = function(val) {

		if (jQuery.inArray(val, $(this)) == -1) {
		  return false; /* Cannot find value in array */
		} else {
		  return true; /* Value found */
		}
	};

	// clear all existing tags and import new ones from a string
	jQuery.fn.importTags = function(str) {
		$('#'+id+'_tagsinput .tag').remove();
		jQuery.fn.tagsInput.importTags(this,str);
	}

	jQuery.fn.tagsInput = function(options) {

		var settings = jQuery.extend({defaultText:'add a tag',minChars:0,width:'300px',height:'100px','hide':true,'delimiter':',',autocomplete:{selectFirst:false},'unique':true},options);

		this.each(function() {
			if (settings.hide) {
				$(this).hide();
			}

			id = $(this).attr('id')

			data = jQuery.extend({
				pid:id,
				real_input: '#'+id,
				holder: '#'+id+'_tagsinput',
				input_wrapper: '#'+id+'_addTag',
				fake_input: '#'+id+'_tag'
			},settings);

			delimiter[id] = data.delimiter;
			if (settings.onAddTag || settings.onRemoveTag) {
				tags_callbacks[id] = new Array();
				tags_callbacks[id]['onAddTag'] = settings.onAddTag;
				tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
			}

			$('

<input id="'+id+'_tag" value="" default="'+settings.defaultText+'" />

').insertAfter(this);

			$(data.holder).css('width',settings.width);
			$(data.holder).css('height',settings.height);

			if ($(data.real_input).val()!='') {
				jQuery.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
			} else {
				$(data.fake_input).val($(data.fake_input).attr('default'));
				$(data.fake_input).css('color','#666666');
			}

			$(data.holder).bind('click',data,function(event) {
				$(event.data.fake_input).focus();
			});

			$(data.fake_input).bind('focus',data,function(event) {
				if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('default')) {
					$(event.data.fake_input).val('');
				}
				$(event.data.fake_input).css('color','#000000');
			});

			if (settings.autocomplete_url != undefined) {
				$(data.fake_input).autocomplete(settings.autocomplete_url,settings.autocomplete).bind('result',data,function(event,data,formatted) {
					if (data)
					{
						$(event.data.real_input).addTag(formatted,{focus:true,unique:(settings.unique)});
					}
				});

				$(data.fake_input).bind('blur',data,function(event) {
					if( $('.ac_results').is(':visible') ) return false;
					if ( $(event.data.fake_input).val() != $(event.data.fake_input).attr('default')) {
						if((event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)))
							$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:false,unique:(settings.unique)});
					}

					$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
					$(event.data.fake_input).css('color','#666666');
					return false;
				});

			} else {
					// if a user tabs out of the field, create a new tag
					// this is only available if autocomplete is not used.
					$(data.fake_input).bind('blur',data,function(event) {
						var d = $(this).attr('default');
						if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
							if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
								$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
						} else {
							$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
							$(event.data.fake_input).css('color','#666666');
						}
						return false;
					});

			}
			// if user types a comma, create a new tag
			$(data.fake_input).bind('keypress',data,function(event) {
				if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) {
					if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
						$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});

					return false;
				}
			});
			$(data.fake_input).blur();
			return false;
		});

		return this;

	};

	jQuery.fn.tagsInput.updateTagsField = function(obj,tagslist) {

			id = $(obj).attr('id');
			$(obj).val(tagslist.join(delimiter[id]));
		};

	jQuery.fn.tagsInput.importTags = function(obj,val) {

			$(obj).val('');
			id = $(obj).attr('id');
			var tags = val.split(delimiter[id]);
			for (i=0; i<tags.length; i++) {
				$(obj).addTag(tags[i],{focus:false});
			}
		};

})(jQuery);
<|endoftext|>