<commit_before>define(function() {
  "use strict";

  // tracking auto-generated IDs
  var counts = {};

  /**
   * Note: we're not using this as an object constructor,
   * merely as the main entrypoint into Ceci for custom
   * elements.
   */
  var Ceci = function (element, buildProperties) {
    Object.keys(buildProperties).filter(function (item) {
      return Ceci._reserved.indexOf(item) === -1;
    }).forEach(function (property) {
      var entry = buildProperties[property];
      if (typeof entry === 'function') {
        element[property] = function() {
          entry.apply(element, arguments);
        };
      }
    });

    element.defaultListener = buildProperties.defaultListener;

    element.subscriptionListeners = [];

    if(buildProperties.listeners) {
      Object.keys(buildProperties.listeners).forEach(function (listener) {
        var entry = buildProperties.listeners[listener];
        var entryType = typeof entry;

        if (entryType === 'function') {
          element[listener] = function() {
            entry.apply(element, arguments);
          };
          element.subscriptionListeners.push(listener);
        } else {
          throw "Listener \"" + listener + "\" is not a function.";
        }
      });
    }

    element.emit = function (data) {
      if(element.broadcastChannel === Ceci.emptyChannel) return;
      var e = new CustomEvent(element.broadcastChannel, {bubbles: true, detail: data});
      element.dispatchEvent(e);
      if(element.onOutputGenerated) {
        element.onOutputGenerated(element.broadcastChannel, data);
      }
      console.log(element.id + " -> " + element.broadcastChannel);
    };

    // init must always be a function, even if it does nothing
    element.init = function () {};
    if(buildProperties.init) {
      element.init = function() {
        buildProperties.init.apply(element, arguments);
      };
    }

    // pass along the broadcast property
    element.broadcast = buildProperties.broadcast;

    // allow for event cleanup when removing an element from the DOM
    element._flatheadListeners = [];

    // add an event listener and record it got added by this element
    element.setupEventListener = function(item, event, fn) {
      item.addEventListener(event, fn);
      element._flatheadListeners.push({
        item: item,
        event: event,
        fn: fn
      });
    };

    // remove a specific event listener associated with this element
    element.discardEventListener = function(item, event, fn) {
      var listeners = element._flatheadListeners;
      for(var i=listeners.length-1, e; i>=0; i--) {
        e = listeners[i];
        if (e.item === item && e.event === event && e.fn === fn) {
          item.removeEventListener(event, fn);
          listeners.splice(i,1);
          return;
        }
      }
    };

    // remove this element from the DOM, after cleaning up all
    // outstanding event listeners
    element.removeSafely = function() {
      element._flatheadListeners.forEach(function(e) {
        e.item.removeEventListener(e.event, e.fn);
      });
      element._flatheadListeners = [];
      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }
    };

    // run any plugins that hook into the constructor
    Ceci._plugins.constructor.forEach(function(plugin) {
      plugin(element, buildProperties);
    });
  };

  // administrative values and objects
  Ceci._reserved = ['init', 'listeners', 'defaultListener'];
  Ceci._plugins = {
    constructor: [],
    onload: []
  };
  Ceci._defaultBroadcastChannel = "blue";
  Ceci._defaultListeningChannel = "blue";
  Object.defineProperty(Ceci, "emptyChannel", {
    enumerable: false,
    configurable: false,
    writable: false,
    value: "false"
  });
  Ceci._components = {};

  /**
   * Register a plugin into Ceci
   */
  Ceci.registerCeciPlugin = function(eventName, plugin) {
    Ceci._plugins[eventName].push(plugin);
  };

  /**
   * Plugins can add additional reserved words to Ceci's list
   */
  Ceci.reserveKeyword = function(keyword) {
    Ceci._reserved.push(keyword);
  };

  /**
   * This function is only called once, when an element
   * is instantiated, and returns the name of the channel
   * the element should be listening to "by default".
   */
  function getBroadcastChannel(element, original) {
    // get <broadcast> element information
    var broadcast = original.getElementsByTagName('broadcast')[0];
    if (broadcast){
      var channel = broadcast.getAttribute("on");
      if (channel) {
        return channel;
      }
    }
    // if no broadcast channel is specified, but this is a broadcast
    // element, use the default channel. Otherwise, don't broadcast
    return (element.broadcast ? Ceci._defaultBroadcastChannel : Ceci.emptyChannel);
  }

  /**
   * Set up the broadcasting behaviour for an element, based
   * on the broadcasting properties it inherited from the
   * <element> component master.
   */
  function setupBroadcastLogic(element, original) {
    // get <broadcast> rules from the original declaration
    element.broadcastChannel = getBroadcastChannel(element, original);
    if(element.onBroadcastChannelChanged) {
      element.onBroadcastChannelChanged(element.broadcastChannel);
    }
    // set property on actual on-page element
    element.setBroadcastChannel = function(channel) {
      element.broadcastChannel = channel;
      if(element.onBroadcastChannelChanged) {
        element.onBroadcastChannelChanged(channel);
      }
    };
  }

  /**
   * This function is only called once, when an element is
   * instantiated, and returns the list of channel subscriptions
   * this element is supposed to have, "by default".
   */
  function getSubscriptions(element, original) {
    var listenElements = original.getElementsByTagName('listen');
    listenElements = Array.prototype.slice.call(listenElements);

    var predefinedListeners = [];
    var subscriptions = listenElements.map(function (e) {
      predefinedListeners.push(e.getAttribute("for"));
      return {
        listener: e.getAttribute("for"),
        channel: e.getAttribute("on")
      };
    });

    element.subscriptionListeners.forEach(function(listener) {
      if(predefinedListeners.indexOf(listener) !== -1) return;
      var subscription = {
        channel: Ceci.emptyChannel,
        listener: listener
      };
      if(listener === element.defaultListener) {
        subscription.channel = Ceci._defaultListeningChannel;
      }
      subscriptions.push(subscription);
    });

    return subscriptions;
  }

  /**
   * Set up the listening behaviour for an element, based
   * on the broadcasting properties it inherited from the
   * <element> component master.
   */
  function setupSubscriptionLogic(element, original) {
    // get <listen> rules from the original declaration
    element.subscriptions = getSubscriptions(element, original);
    if(element.onSubscriptionChannelChanged) {
      element.subscriptions.forEach(function(s){
        element.onSubscriptionChannelChanged(s.channel, s.listener);
      });
    }
    var generateListener = function(element, channel, listener) {
      return function(e) {
        if(e.target.id !== element.id) {
          console.log(element.id + " <- " + channel + "/" + listener);
          var data = e.detail;
          element[listener](data, channel);
          if(element.onInputReceived) {
            element.onInputReceived(channel, data);
          }
        }
      };
    };
    // set properties on actual on-page element
    element.setSubscription = function(channel, listener) {
      var append = true, fn;
      element.subscriptions.forEach(function(s) {
        if(s.listener === listener) {
          // remove the old event listening
          fn = element[listener].listeningFunction;
          if(fn) {
            console.log("removing "+s.channel+"/"+listener+" pair");
            element.discardEventListener(document, s.channel, fn);
          }
          // update the channel
          s.channel = channel;
          // bind the new event listening
          if(channel !== Ceci.emptyChannel) {
            fn = generateListener(element, s.channel, s.listener);
            console.log("adding "+s.channel+"/"+listener+" pair");
            element.setupEventListener(document, s.channel, fn);
          } else {
            fn = false;
          }
          element[listener].listeningFunction = fn;
          append = false;
        }
      });
      if(append) {
        fn = generateListener(element, channel, listener);
        element[listener].listeningFunction = fn;
        console.log("adding "+channel+"/"+listener+" pair");
        element.setupEventListener(document, channel, fn);
        element.subscriptions.push({
          listener: listener,
          channel: channel
        });
      }
      if(element.onSubscriptionChannelChanged) {
        element.onSubscriptionChannelChanged(channel, listener);
      }
    };
    element.removeSubscription = function(channel, listener) {
      var filter = function(s) {
        return !(s.channel === channel && s.listener === listener);
      };
      // single arg: remove listener, regardless of its channel
      if(channel && !listener) {
        listener = channel;
        filter = function(s) {
          return (s.listener !== listener);
        };
      }
      element.subscriptions = element.subscriptions.filter(filter);
    };

    element.subscriptions.forEach(function (s) {
      var fn = generateListener(element, s.channel, s.listener);
      element[s.listener].listeningFunction = fn;
      element.setupEventListener(document, s.channel, fn);
    });
  }

  /**
   * Convert an element of tagname '...' based on the component
   * description for the custom element '...'
   */
  Ceci.convertElement = function (instance, completedHandler) {
    var componentDefinition = Ceci._components[instance.localName],
        originalElement = instance.cloneNode(true);

    // does this instance need an id?
    if(!instance.id) {
      var ln = instance.localName.toLowerCase();
      instance.id = ln + "-" + counts[ln]++;
    }

    // cache pre-conversion content
    instance._innerHTML = instance.innerHTML;
    instance._innerText = instance.innerText;

    // apply the element's template
    if (componentDefinition.template){
      // TODO: should we do a <content></content> replacement?
      instance.innerHTML = componentDefinition.template.innerHTML;
    }

    // if the <element> had a description block, bind this
    // to the instance as well, for future reference.
    if (componentDefinition.description) {
      instance.description = componentDefinition.description;
    }

    // set up the hook for post constructor callbacks
    var finalize = function() {
      finalize.called = true;
      setupBroadcastLogic(instance, originalElement);
      setupSubscriptionLogic(instance, originalElement);
      instance.init();
      if(completedHandler) {
        completedHandler(instance);
      }
    };
    finalize.called = false;

    componentDefinition.constructor.call(instance, finalize);

    if (typeof instance.init === 'function') {
      if(!finalize.called) {
        finalize();
      }
    }
  };

  /**
   * Process an individual <element> so that the element it
   * defines can be used on a web page.
   */
  Ceci.processComponent = function (element) {
    var name = element.getAttribute('name'),
        script = element.querySelector('script[type="text/ceci"]'),
        generator;

    var localName = element.getAttribute("name").toLowerCase();
    if(!counts[localName]) {
      counts[localName] = 0;
    }

    try {
      generator = new Function("Ceci", "return function(callback) {" + script.innerHTML+ "}");
    }
    catch(e){
      if (e.name === 'SyntaxError') {
        e.message += " in definition of component \"" + name + "\".";
        throw e;
      }
      else {
        throw e;
      }
    }
    var constructor = generator(Ceci),
        description = element.querySelector('description'),
        template = element.querySelector('template');

    // Store this element's defining features
    // so that we can reference them when an element
    // with the corresponding tagname is user.
    Ceci._components[name] = {
      constructor: constructor,
      description: description,
      template: template
    };

    // After chronicling, also check whether there happen to
    // already be elements with this tagname on the page that
    // we need to immediately convert
    var existingElements = document.querySelectorAll(name);
    Array.prototype.forEach.call(existingElements, function (existingElement) {
      Ceci.convertElement(existingElement);
    });
  };

  /**
   * Find all <element> elements, and process them so that we
   * can build instances on a page.
   */
  var processComponents = function(fragments, callOnComplete) {
    if(fragments) {
      var elements = fragments.querySelectorAll('element');
      elements = Array.prototype.slice.call(elements);
      elements.forEach(Ceci.processComponent);
    }
    if (callOnComplete){
      callOnComplete(Ceci._components);
    }
  };

  /**
   * Load all web components from <link rel="component">
   */
  Ceci.load = function (callOnComplete) {
    var ceciLinks = document.querySelectorAll('link[rel=component][type="text/ceci"]');

    if (ceciLinks.length === 0) {
      return processComponents(false, callOnComplete);
    }

    var linksLeft = ceciLinks.length,
        fragments = document.createElement("div"),
        loadComponents = function (componentLink) {
          var xhr = new XMLHttpRequest();
          xhr.open('GET', componentLink.getAttribute('href'), true);
          xhr.onload = function (e) {
            var fragment = document.createElement('div');
            fragment.innerHTML = xhr.response;
            fragments.appendChild(fragment);
            if (--linksLeft === 0) {
              processComponents(fragments, callOnComplete);
            }
          };
          xhr.send(null);
        };
    Array.prototype.forEach.call(ceciLinks, loadComponents);
  };

  // and lastly, an AMD module return
  return Ceci;
});
<commit_msg>dehydrate/rehydrate for elements<commit_after>define(function() {
  "use strict";

  // tracking auto-generated IDs
  var counts = {};

  /**
   * Note: we're not using this as an object constructor,
   * merely as the main entrypoint into Ceci for custom
   * elements.
   */
  var Ceci = function (element, buildProperties) {
    Object.keys(buildProperties).filter(function (item) {
      return Ceci._reserved.indexOf(item) === -1;
    }).forEach(function (property) {
      var entry = buildProperties[property];
      if (typeof entry === 'function') {
        element[property] = function() {
          entry.apply(element, arguments);
        };
      }
    });

    if(buildProperties.editable) {
      element.editableAttributes = [];
      Object.keys(buildProperties.editable).forEach(function(attribute) {
        element.editableAttributes.push(attribute);
      });
    }

    element.defaultListener = buildProperties.defaultListener;

    element.subscriptionListeners = [];

    if(buildProperties.listeners) {
      Object.keys(buildProperties.listeners).forEach(function (listener) {
        var entry = buildProperties.listeners[listener];
        var entryType = typeof entry;

        if (entryType === 'function') {
          element[listener] = function() {
            entry.apply(element, arguments);
          };
          element.subscriptionListeners.push(listener);
        } else {
          throw "Listener \"" + listener + "\" is not a function.";
        }
      });
    }

    element.emit = function (data) {
      if(element.broadcastChannel === Ceci.emptyChannel) return;
      var e = new CustomEvent(element.broadcastChannel, {bubbles: true, detail: data});
      element.dispatchEvent(e);
      if(element.onOutputGenerated) {
        element.onOutputGenerated(element.broadcastChannel, data);
      }
      console.log(element.id + " -> " + element.broadcastChannel);
    };

    // init must always be a function, even if it does nothing
    element.init = function () {};
    if(buildProperties.init) {
      element.init = function() {
        buildProperties.init.apply(element, arguments);
      };
    }

    // pass along the broadcast property
    element.broadcast = buildProperties.broadcast;

    // allow for event cleanup when removing an element from the DOM
    element._flatheadListeners = [];

    // add an event listener and record it got added by this element
    element.setupEventListener = function(item, event, fn) {
      item.addEventListener(event, fn);
      element._flatheadListeners.push({
        item: item,
        event: event,
        fn: fn
      });
    };

    // remove a specific event listener associated with this element
    element.discardEventListener = function(item, event, fn) {
      var listeners = element._flatheadListeners;
      for(var i=listeners.length-1, e; i>=0; i--) {
        e = listeners[i];
        if (e.item === item && e.event === event && e.fn === fn) {
          item.removeEventListener(event, fn);
          listeners.splice(i,1);
          return;
        }
      }
    };

    // remove this element from the DOM, after cleaning up all
    // outstanding event listeners
    element.removeSafely = function() {
      element._flatheadListeners.forEach(function(e) {
        e.item.removeEventListener(e.event, e.fn);
      });
      element._flatheadListeners = [];
      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }
    };

    element.describe = function() {
      return Ceci.dehydrate(element);
    };

    // run any plugins that hook into the constructor
    Ceci._plugins.constructor.forEach(function(plugin) {
      plugin(element, buildProperties);
    });
  };

  // administrative values and objects
  Ceci._reserved = ['init', 'listeners', 'defaultListener'];
  Ceci._plugins = {
    constructor: [],
    onload: []
  };
  Ceci._defaultBroadcastChannel = "blue";
  Ceci._defaultListeningChannel = "blue";
  Object.defineProperty(Ceci, "emptyChannel", {
    enumerable: false,
    configurable: false,
    writable: false,
    value: "false"
  });
  Ceci._components = {};

  /**
   * Register a plugin into Ceci
   */
  Ceci.registerCeciPlugin = function(eventName, plugin) {
    Ceci._plugins[eventName].push(plugin);
  };

  /**
   * Plugins can add additional reserved words to Ceci's list
   */
  Ceci.reserveKeyword = function(keyword) {
    Ceci._reserved.push(keyword);
  };

  /**
   * This function is only called once, when an element
   * is instantiated, and returns the name of the channel
   * the element should be listening to "by default".
   */
  function getBroadcastChannel(element, original) {
    // get <broadcast> element information
    var broadcast = original.getElementsByTagName('broadcast')[0];
    if (broadcast){
      var channel = broadcast.getAttribute("on");
      if (channel) {
        return channel;
      }
    }
    // if no broadcast channel is specified, but this is a broadcast
    // element, use the default channel. Otherwise, don't broadcast
    return (element.broadcast ? Ceci._defaultBroadcastChannel : Ceci.emptyChannel);
  }

  /**
   * Set up the broadcasting behaviour for an element, based
   * on the broadcasting properties it inherited from the
   * <element> component master.
   */
  function setupBroadcastLogic(element, original) {
    // get <broadcast> rules from the original declaration
    element.broadcastChannel = getBroadcastChannel(element, original);
    if(element.onBroadcastChannelChanged) {
      element.onBroadcastChannelChanged(element.broadcastChannel);
    }
    // set property on actual on-page element
    element.setBroadcastChannel = function(channel) {
      element.broadcastChannel = channel;
      if(element.onBroadcastChannelChanged) {
        element.onBroadcastChannelChanged(channel);
      }
    };
  }

  /**
   * This function is only called once, when an element is
   * instantiated, and returns the list of channel subscriptions
   * this element is supposed to have, "by default".
   */
  function getSubscriptions(element, original) {
    var listenElements = original.getElementsByTagName('listen');
    listenElements = Array.prototype.slice.call(listenElements);

    var predefinedListeners = [];
    var subscriptions = listenElements.map(function (e) {
      predefinedListeners.push(e.getAttribute("for"));
      return {
        listener: e.getAttribute("for"),
        channel: e.getAttribute("on")
      };
    });

    element.subscriptionListeners.forEach(function(listener) {
      if(predefinedListeners.indexOf(listener) !== -1) return;
      var subscription = {
        channel: Ceci.emptyChannel,
        listener: listener
      };
      if(listener === element.defaultListener) {
        subscription.channel = Ceci._defaultListeningChannel;
      }
      subscriptions.push(subscription);
    });

    return subscriptions;
  }

  /**
   * Set up the listening behaviour for an element, based
   * on the broadcasting properties it inherited from the
   * <element> component master.
   */
  function setupSubscriptionLogic(element, original) {
    // get <listen> rules from the original declaration
    element.subscriptions = getSubscriptions(element, original);
    if(element.onSubscriptionChannelChanged) {
      element.subscriptions.forEach(function(s){
        element.onSubscriptionChannelChanged(s.channel, s.listener);
      });
    }
    var generateListener = function(element, channel, listener) {
      return function(e) {
        if(e.target.id !== element.id) {
          console.log(element.id + " <- " + channel + "/" + listener);
          var data = e.detail;
          element[listener](data, channel);
          if(element.onInputReceived) {
            element.onInputReceived(channel, data);
          }
        }
      };
    };
    // set properties on actual on-page element
    element.setSubscription = function(channel, listener) {
      var append = true, fn;
      element.subscriptions.forEach(function(s) {
        if(s.listener === listener) {
          // remove the old event listening
          fn = element[listener].listeningFunction;
          if(fn) {
            console.log("removing "+s.channel+"/"+listener+" pair");
            element.discardEventListener(document, s.channel, fn);
          }
          // update the channel
          s.channel = channel;
          // bind the new event listening
          if(channel !== Ceci.emptyChannel) {
            fn = generateListener(element, s.channel, s.listener);
            console.log("adding "+s.channel+"/"+listener+" pair");
            element.setupEventListener(document, s.channel, fn);
          } else {
            fn = false;
          }
          element[listener].listeningFunction = fn;
          append = false;
        }
      });
      if(append) {
        fn = generateListener(element, channel, listener);
        element[listener].listeningFunction = fn;
        console.log("adding "+channel+"/"+listener+" pair");
        element.setupEventListener(document, channel, fn);
        element.subscriptions.push({
          listener: listener,
          channel: channel
        });
      }
      if(element.onSubscriptionChannelChanged) {
        element.onSubscriptionChannelChanged(channel, listener);
      }
    };
    element.removeSubscription = function(channel, listener) {
      var filter = function(s) {
        return !(s.channel === channel && s.listener === listener);
      };
      // single arg: remove listener, regardless of its channel
      if(channel && !listener) {
        listener = channel;
        filter = function(s) {
          return (s.listener !== listener);
        };
      }
      element.subscriptions = element.subscriptions.filter(filter);
    };

    element.subscriptions.forEach(function (s) {
      var fn = generateListener(element, s.channel, s.listener);
      element[s.listener].listeningFunction = fn;
      element.setupEventListener(document, s.channel, fn);
    });
  }

  // describe an element as a terse JSON object
  Ceci.dehydrate = function(element) {
    return {
      tagname: element.localName,
      id: element.id,
      broadcast: element.broadcastChannel,
      listen: element.subscriptions.slice(),
      attributes: (function() {
        if (!element.editableAttributes) return [];
        return element.editableAttributes.map(function(attribute) {
          var value = element.getAttribute(attribute);
          if(!value) return false;
          return {
            name: attribute,
            value: value
          };
        }).filter(function(v) { return !!v; });
      }())
    };
  };

  /**
   * Converts a JSON object into a regular object. For converting.
   */
  Ceci.rehydrate = function(description) {
    var element = document.createElement(description.tagname);
    element.id = description.id;
    var content = "";
    if(description.broadcast.channel !== Ceci.emptyChannel) {
      content += '<broadcast on="'+description.broadcast+'"></broadcast>\n';
    }
    description.listen.forEach(function(listen) {
      if(listen.channel !== Ceci.emptyChannel) {
        content += '<listen on="'+listen.channel+'" for="'+listen.listener+'"></listne>\n';
      }
    });
    element.innerHTML = content;
    description.attributes.forEach(function(item) {
      element.setAttribute(item.name, item.value);
    });
    return element;
  };

  /**
   * Convert an element of tagname '...' based on the component
   * description for the custom element '...'
   */
  Ceci.convertElement = function (instance, completedHandler) {
    var componentDefinition = Ceci._components[instance.localName],
        originalElement = instance.cloneNode(true);

    // does this instance need an id?
    if(!instance.id) {
      var ln = instance.localName.toLowerCase();
      instance.id = ln + "-" + counts[ln]++;
    }

    // cache pre-conversion content
    instance._innerHTML = instance.innerHTML;
    instance._innerText = instance.innerText;

    // apply the element's template
    if (componentDefinition.template){
      // TODO: should we do a <content></content> replacement?
      instance.innerHTML = componentDefinition.template.innerHTML;
    }

    // if the <element> had a description block, bind this
    // to the instance as well, for future reference.
    if (componentDefinition.description) {
      instance.description = componentDefinition.description;
    }

    // set up the hook for post constructor callbacks
    var finalize = function() {
      finalize.called = true;
      setupBroadcastLogic(instance, originalElement);
      setupSubscriptionLogic(instance, originalElement);
      instance.init();
      if(completedHandler) {
        completedHandler(instance);
      }
    };
    finalize.called = false;

    componentDefinition.constructor.call(instance, finalize);

    if (typeof instance.init === 'function') {
      if(!finalize.called) {
        finalize();
      }
    }
  };

  /**
   * Process an individual <element> so that the element it
   * defines can be used on a web page.
   */
  Ceci.processComponent = function (element) {
    var name = element.getAttribute('name'),
        script = element.querySelector('script[type="text/ceci"]'),
        generator;

    var localName = element.getAttribute("name").toLowerCase();
    if(!counts[localName]) {
      counts[localName] = 1;
    }

    try {
      generator = new Function("Ceci", "return function(callback) {" + script.innerHTML+ "}");
    }
    catch(e){
      if (e.name === 'SyntaxError') {
        e.message += " in definition of component \"" + name + "\".";
        throw e;
      }
      else {
        throw e;
      }
    }
    var constructor = generator(Ceci),
        description = element.querySelector('description'),
        template = element.querySelector('template');

    // Store this element's defining features
    // so that we can reference them when an element
    // with the corresponding tagname is user.
    Ceci._components[name] = {
      constructor: constructor,
      description: description,
      template: template
    };

    // After chronicling, also check whether there happen to
    // already be elements with this tagname on the page that
    // we need to immediately convert
    var existingElements = document.querySelectorAll(name);
    Array.prototype.forEach.call(existingElements, function (existingElement) {
      Ceci.convertElement(existingElement);
    });
  };

  /**
   * Find all <element> elements, and process them so that we
   * can build instances on a page.
   */
  var processComponents = function(fragments, callOnComplete) {
    if(fragments) {
      var elements = fragments.querySelectorAll('element');
      elements = Array.prototype.slice.call(elements);
      elements.forEach(Ceci.processComponent);
    }
    if (callOnComplete){
      callOnComplete(Ceci._components);
    }
  };

  /**
   * Load all web components from <link rel="component">
   */
  Ceci.load = function (callOnComplete) {
    var ceciLinks = document.querySelectorAll('link[rel=component][type="text/ceci"]');

    if (ceciLinks.length === 0) {
      return processComponents(false, callOnComplete);
    }

    var linksLeft = ceciLinks.length,
        fragments = document.createElement("div"),
        loadComponents = function (componentLink) {
          var xhr = new XMLHttpRequest();
          xhr.open('GET', componentLink.getAttribute('href'), true);
          xhr.onload = function (e) {
            var fragment = document.createElement('div');
            fragment.innerHTML = xhr.response;
            fragments.appendChild(fragment);
            if (--linksLeft === 0) {
              processComponents(fragments, callOnComplete);
            }
          };
          xhr.send(null);
        };
    Array.prototype.forEach.call(ceciLinks, loadComponents);
  };

  // and lastly, an AMD module return
  return Ceci;
});
<|endoftext|>


<commit_before>self.importScripts("FastaParsing.js", "MJAlgo.js", "Drawing.js", "coma.js", "LstExtractor.js", "filterSingl.js");

Printer = function () {
    this.indent = "  ";
    this.newline = "\n";
    this.countingOffset = 1;
    this.l = [];
    this.printString = function (s) {
        this.l.push(s);
    };
    this.close = function () {};
    this.toText = function () {
        return this.l.join("");
    }
}

function setProgress(stepsDone, stepsToDo) {
    var progress = Math.floor(stepsDone / stepsToDo * 100);
    progress = Math.min(99, progress);
    self.postMessage([
        {
            "key" : ["progress"],
            "val" : progress
        }
    ]);
}
function setFileVal(i, key, val) {
    self.postMessage([
        {
            "key" : ["faFiles", i, key],
            "val" : val
        }
    ]);
}
function replace(s, i, c) {
    return s.substring(0, i) + c + s.substring(i+1);
}
function calculateFaFile(project, i) {
    try {
        // ok, set that we are processing this file
        setFileVal(i, "start", Date.now());
        setFileVal(i, "status", "running.label");
        // read the file content to a fasta file
        var fc = parsing_FastaParser.parseFasta(project["faFiles"][i]["data"]);
        setProgress(++stepsDone, stepsToDO);
        setFileVal(i, "endParsing", Date.now());
        // end parsing - now run mj algo
        if(typeof project["delimiter"] !== "undefined") {
            mj_Seq.delimiter = project["delimiter"];
        }
        var m = new mj_MJAlgo();
        // check sequence
        var toRem = []
        var current = fc.h;
        while(current != null && current != undefined) {
            var seqName = current.item.first, seq = current.item.second;
            for(var chIndex = 0; chIndex < seq.length; chIndex++) { // XXX: for loop should change place with while loop (or even better whole thing should get implemented in HaXe)
                var c = seq.charAt(chIndex).toUpperCase();
                if(!(c == 'A' || c == 'T' || c == 'G' || c == 'C' || c == '-')) {
                    if(project["ambigStrat"] == "error") {
                        if (c == 'R' || c == 'Y' || c == 'K' || c == 'M' || c == 'S' || c == 'W' || c == 'B' || c == 'D' || c == 'H' || c == 'V' || c == 'N' || c == '?') {
                            throw new js__$Boot_HaxeError("Your input FASTA file contains one or several ambiguities (" + c + "). Please make sure that all your sequences are properly phased and do not contain any ambiguities before running HaplowebMaker. (Or enable an option for treating ambiguities ...)");
                        }
                        throw new js__$Boot_HaxeError("Your input FASTA file contains one or several unauthorized characters (" + c + ")!");
                    } else {
                        toRem.push(chIndex);
                    }
                }
                if(c == '-' && !!project["replaceIndelPositions"]) {
                    toRem.push(chIndex);
                }
            }
            current = current.next;
        }
        for (var ixy = 0; ixy < toRem.length; ixy++) {
            var ppii = toRem[ixy];
            var current = fc.h;
            while(current != null && current != undefined) {
                var seqName = current.item.first, seq = current.item.second;
                seq = replace(seq, ppii, "#");
//                seq = seq.substring(0, ppii) + "#" + seq.substring(ppii + 1);
                current.item.second = seq;
                current = current.next;
            }
        }
        // filter for singletons if needed
        if(!!project["removeSingletons"]) {
            fc = FilterSingletons.filterSingletons(fc);
        }
        // change frequency?
        if(project["frequency"] == "assumeAtLeastDiploid") {
            fc = FilterSingletons.assumeAtLeastDiploid(fc, project["delimiter"]);
        } else if(project["frequency"] == "numberOfIndividuals") {
            fc = FilterSingletons.removeDuplicates(fc, project["delimiter"]);
        }
        // add sequences
        var current = fc.h;
        while(current != null && current != undefined) {
            var seqName = current.item.first, seq = current.item.second;
            // check whether to add warning
            if(seq.charAt(0) == "-" || seq.charAt(seq.length - 1) == "-") {
                if(!self.addedIndelIs5thStateWarning) {
                    project["notifications"].push({ "ui" : "highlight", "icon" : "info", "text" : "indelIs5State.text" });
                    self.postMessage([{ "key" : ["notifications"], "val" : project["notifications"] }]);
                }
                self.addedIndelIs5thStateWarning = true;
            }
            // go on
            if(project["upperLowerCaseN"] && project["upperLowerCaseS"]) {
                m.addSequence(seqName.toUpperCase(), seq.toUpperCase());
            } else if(project["upperLowerCaseN"]) {
                m.addSequence(seqName.toUpperCase(), seq);
            } else if(project["upperLowerCaseS"]) {
                m.addSequence(seqName, seq.toUpperCase());
            } else {
                m.addSequence(seqName, seq);
            }
            current = current.next;
        }
        m.finishedAddingSequences();
        if(!project["onlyComa"]) {
            m.runMJ(project["epsilon"], !!project["onlyMinimumSpanningNetwork"]);
        }
        var p = new Printer();
        m.finalizeNetwork().printTxt(p);
        var netTxt = p.toText();
        project["faFiles"][i]["mj"] = p.toText();
        setFileVal(i, "mj", project["faFiles"][i]["mj"]);
        setFileVal(i, "nrSeqs", m.getNrSeqs());
        setFileVal(i, "nrDifSeqs", m.getNrDifSeqs());
        setFileVal(i, "seqLen", m.getSeqLength());
        setFileVal(i, "intPos", m.getNrInterestingPositions());
        setFileVal(i, "ffrs", m.getNrFFRs());
        setFileVal(i, "inds", m.countIndiv());
        setProgress(++stepsDone, stepsToDO);
        setFileVal(i, "endMJ", Date.now());
        // end of mj - now draw
        if(typeof project["areaShouldBePropToNrInd"] !== "undefined") {
            draw_NodePos.set_areaShouldBePropTo(project["areaShouldBePropToNrInd"]);
        }
        if(!project["onlyComa"]) {
            var g = new draw_Graph(parsing_MJNetParser.parseNet(netTxt));
            g.forceDirectedMethod(true, 0.6, 0.5, 500.0, 0.1, 0.1, 10000);
            g.centerPos();
//            g.stretch(0.1);
            g.assignLinkPos();
            // assign pie charts if needed
            if(typeof project["pieChart"] !== "undefined") {
                var txt = g.assingPiesByTxt(project["pieChart"], project["upperLowerCaseN"], project["pieByIndNameOnly"]);
                if (txt != "") {
                    self.postMessage([
                        {
                            "key" : "warning",
                            "val" : "Missing colors for individual(s) " + txt
                        }
                    ]);
                }
                // TODO handle txt
            } else if(typeof project["assignRandomColoresToFFRs"] !== "undefined" && !!project["assignRandomColoresToFFRs"]) {
                g.colorNetwork();
            }
            // multicolor?
            /*
            if(typeof project["multiColorLinks"] !== "undefined" && project["multiColorLinks"] && typeof project["pieChart"] !== "undefined") {
                g.initStrokeColorListByStr(project["pieChart"], project["upperLowerCaseN"]);
            }
            */
            // resize nodes / lines / curves if needed
            g.normalizeGraph();
            // return result
            setFileVal(i, "graphstyle", g.saveStyle());
            setFileVal(i, "svg", g.getSvgCode());
        }
        setFileVal(i, "endDraw", Date.now());
        // end of drawing - end of this file
        setFileVal(i, "status", "okstatus.label");
    } catch(e) {
console.log("XXX " + e);
        setFileVal(i, "status", "failed.label");
        setFileVal(i, "statusDescription", e.message);
        stepsDone = (i+1) * 3;
        setProgress(stepsDone, stepsToDO);
    }
}

self.addEventListener('message', function(e) {
    var project = e.data;
    self.addedIndelIs5thStateWarning = false;
    // for processbar
    stepsToDO = project["faFiles"].length * 3 + 1; // for each file - parsing, create haploweb, assing svg drawing positions & run coma
    stepsDone = 0;
    // do every file
    for(var i = 0; i < project["faFiles"].length; i++) {
        calculateFaFile(project, i);
    }
    // do coma
//    try {
        var a = new Array(project["faFiles"]);
        var fileNames = new Array(project["faFiles"]);
        for(var i = 0; i < project["faFiles"].length; i++) {
            var p = new Printer();
            LstExtractor.extract(p, project["faFiles"][i]["mj"], true, false, false, false, project["delimiter"]);
            a[i] = p.toText();
            fileNames[i] = project["faFiles"][i]["filename"];
        }
        var p = new Printer();
        var p2 = new Printer();
        var p3 = new Printer();
        CoMa.runComaJS(a, p, p2, p3, fileNames, 0);
        var comaTxt = p.toText();
        var comaLstTxt = p2.toText();
        var partitionsLstTxt = p3.toText();
        self.postMessage([
            {
                "key" : ["coma"],
                "val" : comaTxt
            }
        ]);
        self.postMessage([
            {
                "key" : ["comaLst"],
                "val" : comaLstTxt
            }
        ]);
        self.postMessage([
            {
                "key" : ["partitionsLst"],
                "val" : partitionsLstTxt
            }
        ]);
/*
    } catch(e) {
console.log("XXX " + e);
    }
*/
    // safe end time
    self.postMessage([{ "key" : ["calculationEndDate"], "val" : Date.now() }]);
    self.postMessage([
        {
            "key" : ["progress"],
            "val" : 100
        }
    ]);
}, false);
<commit_msg>Also color links according to color file<commit_after>self.importScripts("FastaParsing.js", "MJAlgo.js", "Drawing.js", "coma.js", "LstExtractor.js", "filterSingl.js");

Printer = function () {
    this.indent = "  ";
    this.newline = "\n";
    this.countingOffset = 1;
    this.l = [];
    this.printString = function (s) {
        this.l.push(s);
    };
    this.close = function () {};
    this.toText = function () {
        return this.l.join("");
    }
}

function setProgress(stepsDone, stepsToDo) {
    var progress = Math.floor(stepsDone / stepsToDo * 100);
    progress = Math.min(99, progress);
    self.postMessage([
        {
            "key" : ["progress"],
            "val" : progress
        }
    ]);
}
function setFileVal(i, key, val) {
    self.postMessage([
        {
            "key" : ["faFiles", i, key],
            "val" : val
        }
    ]);
}
function replace(s, i, c) {
    return s.substring(0, i) + c + s.substring(i+1);
}
function calculateFaFile(project, i) {
    try {
        // ok, set that we are processing this file
        setFileVal(i, "start", Date.now());
        setFileVal(i, "status", "running.label");
        // read the file content to a fasta file
        var fc = parsing_FastaParser.parseFasta(project["faFiles"][i]["data"]);
        setProgress(++stepsDone, stepsToDO);
        setFileVal(i, "endParsing", Date.now());
        // end parsing - now run mj algo
        if(typeof project["delimiter"] !== "undefined") {
            mj_Seq.delimiter = project["delimiter"];
        }
        var m = new mj_MJAlgo();
        // check sequence
        var toRem = []
        var current = fc.h;
        while(current != null && current != undefined) {
            var seqName = current.item.first, seq = current.item.second;
            for(var chIndex = 0; chIndex < seq.length; chIndex++) { // XXX: for loop should change place with while loop (or even better whole thing should get implemented in HaXe)
                var c = seq.charAt(chIndex).toUpperCase();
                if(!(c == 'A' || c == 'T' || c == 'G' || c == 'C' || c == '-')) {
                    if(project["ambigStrat"] == "error") {
                        if (c == 'R' || c == 'Y' || c == 'K' || c == 'M' || c == 'S' || c == 'W' || c == 'B' || c == 'D' || c == 'H' || c == 'V' || c == 'N' || c == '?') {
                            throw new js__$Boot_HaxeError("Your input FASTA file contains one or several ambiguities (" + c + "). Please make sure that all your sequences are properly phased and do not contain any ambiguities before running HaplowebMaker. (Or enable an option for treating ambiguities ...)");
                        }
                        throw new js__$Boot_HaxeError("Your input FASTA file contains one or several unauthorized characters (" + c + ")!");
                    } else {
                        toRem.push(chIndex);
                    }
                }
                if(c == '-' && !!project["replaceIndelPositions"]) {
                    toRem.push(chIndex);
                }
            }
            current = current.next;
        }
        for (var ixy = 0; ixy < toRem.length; ixy++) {
            var ppii = toRem[ixy];
            var current = fc.h;
            while(current != null && current != undefined) {
                var seqName = current.item.first, seq = current.item.second;
                seq = replace(seq, ppii, "#");
//                seq = seq.substring(0, ppii) + "#" + seq.substring(ppii + 1);
                current.item.second = seq;
                current = current.next;
            }
        }
        // filter for singletons if needed
        if(!!project["removeSingletons"]) {
            fc = FilterSingletons.filterSingletons(fc);
        }
        // change frequency?
        if(project["frequency"] == "assumeAtLeastDiploid") {
            fc = FilterSingletons.assumeAtLeastDiploid(fc, project["delimiter"]);
        } else if(project["frequency"] == "numberOfIndividuals") {
            fc = FilterSingletons.removeDuplicates(fc, project["delimiter"]);
        }
        // add sequences
        var current = fc.h;
        while(current != null && current != undefined) {
            var seqName = current.item.first, seq = current.item.second;
            // check whether to add warning
            if(seq.charAt(0) == "-" || seq.charAt(seq.length - 1) == "-") {
                if(!self.addedIndelIs5thStateWarning) {
                    project["notifications"].push({ "ui" : "highlight", "icon" : "info", "text" : "indelIs5State.text" });
                    self.postMessage([{ "key" : ["notifications"], "val" : project["notifications"] }]);
                }
                self.addedIndelIs5thStateWarning = true;
            }
            // go on
            if(project["upperLowerCaseN"] && project["upperLowerCaseS"]) {
                m.addSequence(seqName.toUpperCase(), seq.toUpperCase());
            } else if(project["upperLowerCaseN"]) {
                m.addSequence(seqName.toUpperCase(), seq);
            } else if(project["upperLowerCaseS"]) {
                m.addSequence(seqName, seq.toUpperCase());
            } else {
                m.addSequence(seqName, seq);
            }
            current = current.next;
        }
        m.finishedAddingSequences();
        if(!project["onlyComa"]) {
            m.runMJ(project["epsilon"], !!project["onlyMinimumSpanningNetwork"]);
        }
        var p = new Printer();
        m.finalizeNetwork().printTxt(p);
        var netTxt = p.toText();
        project["faFiles"][i]["mj"] = p.toText();
        setFileVal(i, "mj", project["faFiles"][i]["mj"]);
        setFileVal(i, "nrSeqs", m.getNrSeqs());
        setFileVal(i, "nrDifSeqs", m.getNrDifSeqs());
        setFileVal(i, "seqLen", m.getSeqLength());
        setFileVal(i, "intPos", m.getNrInterestingPositions());
        setFileVal(i, "ffrs", m.getNrFFRs());
        setFileVal(i, "inds", m.countIndiv());
        setProgress(++stepsDone, stepsToDO);
        setFileVal(i, "endMJ", Date.now());
        // end of mj - now draw
        if(typeof project["areaShouldBePropToNrInd"] !== "undefined") {
            draw_NodePos.set_areaShouldBePropTo(project["areaShouldBePropToNrInd"]);
        }
        if(!project["onlyComa"]) {
            var g = new draw_Graph(parsing_MJNetParser.parseNet(netTxt));
            g.forceDirectedMethod(true, 0.6, 0.5, 500.0, 0.1, 0.1, 10000);
            g.centerPos();
//            g.stretch(0.1);
            g.assignLinkPos();
            // assign pie charts if needed
            if(typeof project["pieChart"] !== "undefined") {
                var txt = g.assingPiesByTxt(project["pieChart"], project["upperLowerCaseN"], project["pieByIndNameOnly"]);
                g.initStrokeColorListByStr(project['pieChart'], project["upperLowerCaseN"]);
                if (txt != "") {
                    self.postMessage([
                        {
                            "key" : "warning",
                            "val" : "Missing colors for individual(s) " + txt
                        }
                    ]);
                }
                // TODO handle txt
            } else if(typeof project["assignRandomColoresToFFRs"] !== "undefined" && !!project["assignRandomColoresToFFRs"]) {
                g.colorNetwork();
            }
            // multicolor?
            /*
            if(typeof project["multiColorLinks"] !== "undefined" && project["multiColorLinks"] && typeof project["pieChart"] !== "undefined") {
                g.initStrokeColorListByStr(project["pieChart"], project["upperLowerCaseN"]);
            }
            */
            // resize nodes / lines / curves if needed
            g.normalizeGraph();
            // return result
            setFileVal(i, "graphstyle", g.saveStyle());
            setFileVal(i, "svg", g.getSvgCode());
        }
        setFileVal(i, "endDraw", Date.now());
        // end of drawing - end of this file
        setFileVal(i, "status", "okstatus.label");
    } catch(e) {
console.log("XXX " + e);
        setFileVal(i, "status", "failed.label");
        setFileVal(i, "statusDescription", e.message);
        stepsDone = (i+1) * 3;
        setProgress(stepsDone, stepsToDO);
    }
}

self.addEventListener('message', function(e) {
    var project = e.data;
    self.addedIndelIs5thStateWarning = false;
    // for processbar
    stepsToDO = project["faFiles"].length * 3 + 1; // for each file - parsing, create haploweb, assing svg drawing positions & run coma
    stepsDone = 0;
    // do every file
    for(var i = 0; i < project["faFiles"].length; i++) {
        calculateFaFile(project, i);
    }
    // do coma
//    try {
        var a = new Array(project["faFiles"]);
        var fileNames = new Array(project["faFiles"]);
        for(var i = 0; i < project["faFiles"].length; i++) {
            var p = new Printer();
            LstExtractor.extract(p, project["faFiles"][i]["mj"], true, false, false, false, project["delimiter"]);
            a[i] = p.toText();
            fileNames[i] = project["faFiles"][i]["filename"];
        }
        var p = new Printer();
        var p2 = new Printer();
        var p3 = new Printer();
        CoMa.runComaJS(a, p, p2, p3, fileNames, 0);
        var comaTxt = p.toText();
        var comaLstTxt = p2.toText();
        var partitionsLstTxt = p3.toText();
        self.postMessage([
            {
                "key" : ["coma"],
                "val" : comaTxt
            }
        ]);
        self.postMessage([
            {
                "key" : ["comaLst"],
                "val" : comaLstTxt
            }
        ]);
        self.postMessage([
            {
                "key" : ["partitionsLst"],
                "val" : partitionsLstTxt
            }
        ]);
/*
    } catch(e) {
console.log("XXX " + e);
    }
*/
    // safe end time
    self.postMessage([{ "key" : ["calculationEndDate"], "val" : Date.now() }]);
    self.postMessage([
        {
            "key" : ["progress"],
            "val" : 100
        }
    ]);
}, false);
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import classNames from 'classnames';

import '../css/Components/Button';

const Button = ({ children, alternative }) => {
  const className = classNames({
    'Button': true,
    'Button--alternative': alternative,
  });

  return (

      {children}

  );
};

Button.propTypes = {
  alternative: PropTypes.bool,
  children: PropTypes.node.isRequired,
};

Button.defaultProps = {
  alternative: false,
};

export default Button;
<commit_msg>Fixes button animation<commit_after>import React, { PropTypes } from 'react';
import classNames from 'classnames';

import '../css/Components/Button';

const Button = ({ children, alternative }) => {
  const className = classNames({
    'Button': true,
    'Button--alternative': alternative,
  });

  return (

      {children}

  );
};

Button.propTypes = {
  alternative: PropTypes.bool,
  children: PropTypes.node.isRequired,
};

Button.defaultProps = {
  alternative: false,
};

export default Button;
<|endoftext|>


<commit_before>/** MIT License (c) copyright 2010-2013 B Cavalier & J Hann */

/**
 * cram CommonJS modules/1.1 plugin
 */
define(['./jsEncode', '../loader/cjsm11'], function (jsEncode, wrapCjsm11) {

	return {
		compile: function (pluginId, resId, req, io, config) {
			if (resId.substr(resId.length - 3) !== ".js") {
				resId += ".js";
			}
			io.read(resId, function (text) {
				io.write(jsEncode(wrapCjsm11(text, resId)));
			}, io.error);
		}
	};

});
<commit_msg>Use correct moduleId<commit_after>/** MIT License (c) copyright 2010-2013 B Cavalier & J Hann */

/**
 * cram CommonJS modules/1.1 plugin
 */
define(['./jsEncode', '../loader/cjsm11'], function (jsEncode, wrapCjsm11) {

	return {
		compile: function (pluginId, resId, req, io, config) {
			var moduleId = resId;
			if (resId.substr(resId.length - 3) !== ".js") {
				resId += ".js";
			}
			io.read(resId, function (text) {
				io.write(jsEncode(wrapCjsm11(text, moduleId)));
			}, io.error);
		}
	};

});
<|endoftext|>


<commit_before>/**
 * @file
 * Observer design pattern.
 *
 * This object will provide an observer design pattern. Observers may
 * subscribe to specific message types. Observables may broadcast
 * messages specific to a type.
 *
 * @author Bryan Hazelbaker <bryan.hazelbaker@gmail.com>
 * @version 0.1
 *
 * @see http://www.joezimjs.com/javascript/javascript-design-patterns-observer/
 */

/**
 * The Observable class.
 *
 * Instantiate this to objects that wish to be observed.
 *
 * Example using Observable:
 * @code
 *   // Create the observer object. It is simply a function.
 *   var Observer = function(message, messageType) {
 *       console.log(message);
 *   }
 *   // Create the observable object.
 *   observable = new Observable();
 *   // Create a default message type for observers to subscribe to.
 *   observable.messageTypeAdd('generic');
 *   // Subscribe previously declared Observer all 'generic' messages.
 *   observable.messageSubscribe(Observer, 'generic');
 *   // Publish a message.
 *   observable.messagePublish('generic', 'I published this data!', observable);
 *   // Optionally examination the response of any observers.
 * @endcode
 *
 * Example extending Observable:
 * @code
 *   // Declare the subclass.
 *   var SubObserverableClass = function() {
 *       // Call our super class constructor.
 *       Observable.call(this)
 *       // Register a message type.
 *       this.typeAdd('generic');
 *   };
 *   // Cause our subclass to inherit methods of Observable superclass.
 *   SubObservableClass.prototype = Observable.prototype;
 *   // Add a new method to our new subclass.
 *   SubObservableClass.prototype.myCustomMethod = function() {
 *       alert('My new custom method added to existing Observable methods!');
 *   };
 * @endcode
 */
var Observable = function() {
    this.subscribers = [];
};
Observable.prototype = {
    typeAdd: function(messageType) {
        this.subscribers[messageType] = [];
    },
    typeRemove: function (messageType) {
        var i = 0,
            len = this.subscribers.length;
        // Iterate through the array and if the callback is
        // found, remove it from the list of subscribers.
        for (; i < len; i++) {
            if (this.subscribers[i] === messageType) {
                this.subscribers.splice(i, 1);
                // Once we've found it, we don't need to
                // continue, so just return.
                return;
            }
        }
    },
    /**
     * Determine if a message type exists or not.
     *
     * @param string messageType
     *   Name of the message type.
     *
     * @return bool
     *   true if the type exists, false otherwise.
     */
    typeExists: function(messageType) {
        exists = false;
        if (typeof(this.subscribers[messageType]) != 'undefined') {
            exists = true;
        }
        return exists;
    },
    subscribe: function(messageType, callback) {
        // Automatically add the message type if it does not already exist.
        if (!this.typeExists(messageType)) {
            this.typeAdd(messageType);
        }
        // Add the callback to the message type specific callback list.
        this.subscribers[messageType].push(callback);
    },
    unsubscribe: function(messageType, callback) {
        if (messageType in this.subscribers) {
            var i = 0,
                len = this.subscribers[messageType].length;
            // Iterate through the array and if the callback is
            // found, remove it from the list of subscribers.
            for (; i < len; i++) {
                if (this.subscribers[messageType][i] === callback) {
                    this.subscribers[messageType].splice(i, 1);
                    // Once we've found it, we don't need to
                    // continue, so just return.
                    return;
                }
            }
        }
    },
    /**
     * Send out a message to any listeners.
     *
     * @param mixed data
     *   The data being distributed.
     * @param string messageType
     *   The type of message being distributed.
     * @param object observed
     *   The object that is distributing the message.
     *
     * @return bool|object
     *   false if no response was provider by observers. Otherwise an object
     *   keyed by observer callback function name.
     */
    publish: function(data, messageType, observed) {
        var result = {};
        if (messageType in this.subscribers) {
            var i = 0,
                len = this.subscribers[messageType].length;
            // Iterate over the subscribers array and call each of
            // the callback functions.
            for (; i < len; i++) {
                var callback = this.subscribers[messageType][i];
                result[callback.name] = callback(data, messageType, observed, result);
            }
        }
        return (Object.keys(result).length) ? result : false;
    }
};
<commit_msg>publish function blocks DOM redraw.<commit_after>/**
 * @file
 * Observer design pattern.
 *
 * This object will provide an observer design pattern. Observers may
 * subscribe to specific message types. Observables may broadcast
 * messages specific to a type.
 *
 * @author Bryan Hazelbaker <bryan.hazelbaker@gmail.com>
 * @version 0.1
 *
 * @see http://www.joezimjs.com/javascript/javascript-design-patterns-observer/
 */

/**
 * The Observable class.
 *
 * Instantiate this to objects that wish to be observed.
 *
 * Example using Observable:
 * @code
 *   // Create the observer object. It is simply a function.
 *   var Observer = function(message, messageType) {
 *       console.log(message);
 *   }
 *   // Create the observable object.
 *   observable = new Observable();
 *   // Create a default message type for observers to subscribe to.
 *   observable.messageTypeAdd('generic');
 *   // Subscribe previously declared Observer all 'generic' messages.
 *   observable.messageSubscribe(Observer, 'generic');
 *   // Publish a message.
 *   observable.messagePublish('generic', 'I published this data!', observable);
 *   // Optionally examination the response of any observers.
 * @endcode
 *
 * Example extending Observable:
 * @code
 *   // Declare the subclass.
 *   var SubObserverableClass = function() {
 *       // Call our super class constructor.
 *       Observable.call(this)
 *       // Register a message type.
 *       this.typeAdd('generic');
 *   };
 *   // Cause our subclass to inherit methods of Observable superclass.
 *   SubObservableClass.prototype = Observable.prototype;
 *   // Add a new method to our new subclass.
 *   SubObservableClass.prototype.myCustomMethod = function() {
 *       alert('My new custom method added to existing Observable methods!');
 *   };
 * @endcode
 */
var Observable = function() {
    this.subscribers = [];
};
Observable.prototype = {
    typeAdd: function(messageType) {
        this.subscribers[messageType] = [];
    },
    typeRemove: function (messageType) {
        var i = 0,
            len = this.subscribers.length;
        // Iterate through the array and if the callback is
        // found, remove it from the list of subscribers.
        for (; i < len; i++) {
            if (this.subscribers[i] === messageType) {
                this.subscribers.splice(i, 1);
                // Once we've found it, we don't need to
                // continue, so just return.
                return;
            }
        }
    },
    /**
     * Determine if a message type exists or not.
     *
     * @param string messageType
     *   Name of the message type.
     *
     * @return bool
     *   true if the type exists, false otherwise.
     */
    typeExists: function(messageType) {
        exists = false;
        if (typeof(this.subscribers[messageType]) != 'undefined') {
            exists = true;
        }
        return exists;
    },
    subscribe: function(messageType, callback) {
        // Automatically add the message type if it does not already exist.
        if (!this.typeExists(messageType)) {
            this.typeAdd(messageType);
        }
        // Add the callback to the message type specific callback list.
        this.subscribers[messageType].push(callback);
    },
    unsubscribe: function(messageType, callback) {
        if (messageType in this.subscribers) {
            var i = 0,
                len = this.subscribers[messageType].length;
            // Iterate through the array and if the callback is
            // found, remove it from the list of subscribers.
            for (; i < len; i++) {
                if (this.subscribers[messageType][i] === callback) {
                    this.subscribers[messageType].splice(i, 1);
                    // Once we've found it, we don't need to
                    // continue, so just return.
                    return;
                }
            }
        }
    },
    /**
     * Send out a message to any listeners.
     *
     * @param mixed data
     *   The data being distributed.
     * @param string messageType
     *   The type of message being distributed.
     * @param object observed
     *   The object that is distributing the message.
     *
     * @return bool|object
     *   false if no response was provider by observers. Otherwise an object
     *   keyed by observer callback function name.
     */
    publish: function(data, messageType, observed) {
        var result = {};
        if (messageType in this.subscribers) {
            var i = 0,
                len = this.subscribers[messageType].length;
            // Iterate over the subscribers array and call each of
            // the callback functions.
            for (; i < len; i++) {
                var callback = this.subscribers[messageType][i];
                result[callback.name] = callback(data, messageType, observed, result);
                // This loop seems to block a DOM redraw...
            }
        }
        return (Object.keys(result).length) ? result : false;
    }
};
<|endoftext|>


<commit_before>function get() {
	var xhReq;
	xhReq = new XMLHttpRequest();
	xhReq.open("GET", "ajax.php?q=get", false);

	xhReq.addEventListener("load", function() {

		var msgbox;
		msgbox = document.querySelector("#msgbox");
		msgbox.innerHTML = xhReq.responseText;

		setTimeout(get, 1000);

	});

	xhReq.send(null);
}

function send(msg, nick) {
	var xhReq;
	xhReq = new XMLHttpRequest();
	xhReq.open("GET", "ajax.php?q=send&msg=" + msg + "&nick=" + nick, false);

	xhReq.addEventListener("load", function() {
		console.log(xhReq.responseText);
	});

	xhReq.send(null);
}

function init() {

	var button;
	button = document.querySelector("#send");

	button.addEventListener("click", function() {
		var msg;
		msg = document.querySelector("#msg").value;

		var nick;
		nick = document.querySelector("#nick").value;

		send(msg, nick);
	});

	get();

}

document.addEventListener("DOMContentLoaded", function() {

	init();

});
<commit_msg>Delete chat.js<commit_after><|endoftext|>


<commit_before>/*
 * phonegap.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['./base', '../../config', '../API'], function(fileSystem, config, API){
  return function(){
    fileSystem.dataPath = null;

    fileSystem.correctLocalFilePath = function(path) {
      if (path === '/') path = '';
      return (fileSystem.dataSubFolder + '/' + path.replace(fileSystem.dataPath, '').replace(fileSystem.dataSubFolder + '/', '')).replace(fileSystem.dataSubFolder + '/' + fileSystem.dataSubFolder, fileSystem.dataSubFolder + '/');
    };

    fileSystem.writeFile = function(filename, data, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      fileSystem.getFileSystem(function(fs){
        fileSystem.mkdir(fileSystem.extractDirectory(filename), 0744, function(dirEntry){
          dirEntry.getFile(fileSystem.extractFilename(filename), {create: true}, function(fileEntry){

            var fileURL = fileEntry.toURL();

            fileEntry.createWriter(function(writer){

              writer.onwrite = function(evt){
                if (callback) {
                  callback(fileURL);
                }
              };

              writer.onerror = function(evt){
                if (errorCallback) {
                  errorCallback(evt.target.error);
                }
              };

              writer.write(data);

            }, errorCallback);

          }, errorCallback);
        }, errorCallback);
      }, errorCallback);
    };

    fileSystem.readFile = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(fileSystem.extractDirectory(filename), {create: false, exclusive: false}, function(dirEntry){
          dirEntry.getFile(fileSystem.extractFilename(filename), {create: false}, function(fileEntry){

            fileEntry.file(function(file){

              var reader = new FileReader();

              reader.onloadend = function(evt) {
                if (callback) {
                  callback(evt.target.result);
                }
              };

              reader.onerror =  function(evt){
                if (errorCallback) {
                  errorCallback();
                }
              };

              reader.readAsText(file);

            }, errorCallback);

          }, errorCallback);
        }, errorCallback);
      }, errorCallback);
    };

    fileSystem.readFileBinary = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(fileSystem.extractDirectory(filename), {create: false, exclusive: false}, function(dirEntry){
          dirEntry.getFile(fileSystem.extractFilename(filename), {create: false}, function(fileEntry){

            fileEntry.file(function(file){

              var reader = new FileReader();

              reader.onloadend = function(evt) {
                if (callback) {
                  callback(evt.target.result);
                }
              };

              reader.onerror =  function(evt){
                if (errorCallback) {
                  errorCallback();
                }
              };

              reader.readAsBinaryString(file);

            }, errorCallback);

          }, errorCallback);
        }, errorCallback);
      }, errorCallback);
    };

    fileSystem.pipe = function(options){
      options.local = fileSystem.correctLocalFilePath(options.local);

      // get directory out of local path
      var directory = fileSystem.getFolder(options.local);

      // otherwise ensure local directory exists
      fileSystem.mkdir(directory, 0744, function(){
        // create a temporary file stream (move file to real path when finished correctly)
        var fileTransfer = new FileTransfer();
        var uri = encodeURI(options.remote);

        var downloadOptions = {};

        if (options.remote.indexOf(API.endpoint) >= 0) {
          var token = API.get('token');
          if (token && token.length > 0) {
            downloadOptions.headers = {authorization: 'Bearer ' + token};
          }
        }

        if (options.progress) {
          fileTransfer.onprogress = function(progressEvent) {
            if (progressEvent.lengthComputable) {
              options.progress(progressEvent.loaded / progressEvent.total);
            }
          };
        }

        var success = function(entry) {
          fileSystem.rename(options.local + '_tmp.file', options.local, function(){
            if (options.success) {
              options.success(options.local);
            }
          });
        };

        var error = function(error){
          if (options.error) {
            options.error(error);
          }
        };

        var localFileName = fileSystem.dataBasePath + options.local + '_tmp.file';
        var trustAllHosts = false;

        fileTransfer.download(uri, localFileName, success, error, trustAllHosts, downloadOptions);
      });
    };

    fileSystem.readdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      var fail = function(){
        if (callback) {
          callback([]);
        }
      };

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(path, {create: false, exclusive: false}, function(dirEntry){
          var directoryReader = dirEntry.createReader();
          directoryReader.readEntries(function(entries){
            var flist = [];

            for (var i = 0; i < entries.length; i++){
              flist.push(entries[i].name);
            }

            if (callback) {
              callback(flist);
            }
          }, fail);
        }, fail);
      }, fail);
    };

    fileSystem.exists = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      var dirName = fileSystem.extractDirectory(path);
      var fileName = fileSystem.extractFilename(path);

      var success = function(flist){
        for (var i = 0; i < flist.length; i++){
          if (flist[i].match(fileName)){
            if (callback) {
              callback(true);
            }

            return;
          }
        }

        if (callback) {
          callback(false);
        }
      };

      var error = function(){
        if (callback) {
          callback(false);
        }
      };

      fileSystem.readdir(dirName, success, error);
    };

    fileSystem.rmdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      var cleanup = function(directory, callback){
        if (directory.substring(directory.length - 1) === '/') directory = directory.substring(0, directory.length - 1);

        fileSystem.readdir(directory, function(entries){

          var checkEntry = function(index){
            if (entries[index]) {
              if (entries[index].indexOf('.') >= 0) {
                fileSystem.unlink(directory + '/' + entries[index], function(){
                  checkEntry(index + 1);
                });
              } else {
                cleanup(directory + '/' + entries[index], function(){
                  fileSystem.removeDirectory(directory + '/' + entries[index]);
                  checkEntry(index + 1);
                });
              }
            } else {
              if (callback) {
                callback();
              }
            }
          };

          checkEntry(0);

        });
      };

      fileSystem.getFileSystem(function(fs){
        cleanup(path, function(){
          fileSystem.removeDirectory(path, callback);
        });
      }, callback);
    };

    fileSystem.removeDirectory = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(path, {create: false, exclusive: false}, function(dirEntry){
          dirEntry.remove(callback, callback);
        }, callback);
      }, callback);
    };

    fileSystem.mkdir = function(path, mode, callback, error){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      if (! error) {
        error = callback;
      }

      var folders = path.split('/');
      var folderIndex = 0;

      var createFolder = function(dirEntry){
        var path = folders[folderIndex];

        if (path && path.length > 0) {
          dirEntry.getDirectory(path, {create: true, exclusive: false}, createdFolder, error);
        } else {
          if (callback) {
            callback(dirEntry);
          }
        }
      };

      var createdFolder = function(dirEntry){
        folderIndex++;
        createFolder(dirEntry);
      };

      fileSystem.getFileSystem(function(fs){
        createFolder(fs.root);
      }, callback);
    };

    fileSystem.unlink = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      if (path.indexOf('.') >= 0) {
        fileSystem.getFileSystem(function(fs){
          fs.root.getDirectory(fileSystem.extractDirectory(path), {create: false, exclusive: false}, function(dirEntry){
            dirEntry.getFile(fileSystem.extractFilename(path), {create: false}, function(fileEntry){
              fileEntry.remove(callback, callback);
            }, callback);
          }, callback);
        }, callback);
      } else {
        fileSystem.rmdir(path, callback);
      }
    };

    fileSystem.rename = function(path, newPath, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);
      newPath = fileSystem.correctLocalFilePath(newPath);

      var move = function(entry, toDirectory, withFilename){
        entry.moveTo(toDirectory, withFilename, callback, callback);
      };

      fileSystem.getFileSystem(function(fs){
        fileSystem.mkdir(fileSystem.extractDirectory(path), 0744, function(dirEntry){
          fileSystem.mkdir(fileSystem.extractDirectory(newPath), 0744, function(dirEntryNew){
            // if it is a file
            if (path.indexOf('.') >= 0) {
              dirEntry.getFile(fileSystem.extractFilename(path), {create: false}, function(entry){
                move(entry, dirEntryNew, fileSystem.extractFilename(newPath));
              });
            } else {
              dirEntry.getDirectory(fileSystem.extractFilename(path), {create: false}, function(entry){
                move(entry, dirEntryNew, fileSystem.extractFilename(newPath));
              });
            }
          });
        });
      }, callback);
    };

    fileSystem.checksum = function(path, callback){
      // correct file path
      path = fileSystem.getLocalFilePath(path);

      md5chksum.file(path, function(sum) {
        if (sum && sum.length > 0) {
          if (callback) {
            callback(sum);
          }
        } else {
          if (callback) {
            callback('');
          }
        }
      },

      function(){
        if (callback) {
          callback('');
        }
      });
    };

    fileSystem.init = function(callback){
      fileSystem.getFileSystem(function(fs){
        fileSystem.dataBasePath = fs.root.nativeURL.replace('file://', '');
        fileSystem.dataPath = fileSystem.dataBasePath + fileSystem.dataSubFolder + '/';

        if (config.debug) {
          console.log(fileSystem.dataBasePath);
        }

        if (callback) {
          callback();
        }
      });
    };

    var _cachedFileSystem = null;

    fileSystem.getFileSystem = function(callback){
      if (_cachedFileSystem) {
        if (callback) {
          callback(_cachedFileSystem);
        }
      } else {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs){
          _cachedFileSystem = fs;

          if (callback) {
            callback(fs);
          }
        });
      }
    };

    fileSystem.extractDirectory = function(path){
      var dirPath;
      var lastSlash = path.lastIndexOf('/');

      if (lastSlash === -1) {
        dirPath = '/';
      } else {
        dirPath = path.substring(0, lastSlash);

        if (dirPath === '') {
          dirPath = '/';
        }
      }

      return dirPath;
    };

    fileSystem.extractFilename = function(path){
      var lastSlash = path.lastIndexOf('/');

      if (lastSlash === -1){
        return path;
      }

      var filename =  path.substring(lastSlash + 1);

      return filename;
    };
  };
});
<commit_msg>fail checksum silently after a while<commit_after>/*
 * phonegap.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['./base', '../../config', '../API'], function(fileSystem, config, API){
  return function(){
    fileSystem.dataPath = null;

    fileSystem.correctLocalFilePath = function(path) {
      if (path === '/') path = '';
      return (fileSystem.dataSubFolder + '/' + path.replace(fileSystem.dataPath, '').replace(fileSystem.dataSubFolder + '/', '')).replace(fileSystem.dataSubFolder + '/' + fileSystem.dataSubFolder, fileSystem.dataSubFolder + '/');
    };

    fileSystem.writeFile = function(filename, data, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      fileSystem.getFileSystem(function(fs){
        fileSystem.mkdir(fileSystem.extractDirectory(filename), 0744, function(dirEntry){
          dirEntry.getFile(fileSystem.extractFilename(filename), {create: true}, function(fileEntry){

            var fileURL = fileEntry.toURL();

            fileEntry.createWriter(function(writer){

              writer.onwrite = function(evt){
                if (callback) {
                  callback(fileURL);
                }
              };

              writer.onerror = function(evt){
                if (errorCallback) {
                  errorCallback(evt.target.error);
                }
              };

              writer.write(data);

            }, errorCallback);

          }, errorCallback);
        }, errorCallback);
      }, errorCallback);
    };

    fileSystem.readFile = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(fileSystem.extractDirectory(filename), {create: false, exclusive: false}, function(dirEntry){
          dirEntry.getFile(fileSystem.extractFilename(filename), {create: false}, function(fileEntry){

            fileEntry.file(function(file){

              var reader = new FileReader();

              reader.onloadend = function(evt) {
                if (callback) {
                  callback(evt.target.result);
                }
              };

              reader.onerror =  function(evt){
                if (errorCallback) {
                  errorCallback();
                }
              };

              reader.readAsText(file);

            }, errorCallback);

          }, errorCallback);
        }, errorCallback);
      }, errorCallback);
    };

    fileSystem.readFileBinary = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(fileSystem.extractDirectory(filename), {create: false, exclusive: false}, function(dirEntry){
          dirEntry.getFile(fileSystem.extractFilename(filename), {create: false}, function(fileEntry){

            fileEntry.file(function(file){

              var reader = new FileReader();

              reader.onloadend = function(evt) {
                if (callback) {
                  callback(evt.target.result);
                }
              };

              reader.onerror =  function(evt){
                if (errorCallback) {
                  errorCallback();
                }
              };

              reader.readAsBinaryString(file);

            }, errorCallback);

          }, errorCallback);
        }, errorCallback);
      }, errorCallback);
    };

    fileSystem.pipe = function(options){
      options.local = fileSystem.correctLocalFilePath(options.local);

      // get directory out of local path
      var directory = fileSystem.getFolder(options.local);

      // otherwise ensure local directory exists
      fileSystem.mkdir(directory, 0744, function(){
        // create a temporary file stream (move file to real path when finished correctly)
        var fileTransfer = new FileTransfer();
        var uri = encodeURI(options.remote);

        var downloadOptions = {};

        if (options.remote.indexOf(API.endpoint) >= 0) {
          var token = API.get('token');
          if (token && token.length > 0) {
            downloadOptions.headers = {authorization: 'Bearer ' + token};
          }
        }

        if (options.progress) {
          fileTransfer.onprogress = function(progressEvent) {
            if (progressEvent.lengthComputable) {
              options.progress(progressEvent.loaded / progressEvent.total);
            }
          };
        }

        var success = function(entry) {
          fileSystem.rename(options.local + '_tmp.file', options.local, function(){
            if (options.success) {
              options.success(options.local);
            }
          });
        };

        var error = function(error){
          if (options.error) {
            options.error(error);
          }
        };

        var localFileName = fileSystem.dataBasePath + options.local + '_tmp.file';
        var trustAllHosts = false;

        fileTransfer.download(uri, localFileName, success, error, trustAllHosts, downloadOptions);
      });
    };

    fileSystem.readdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      var fail = function(){
        if (callback) {
          callback([]);
        }
      };

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(path, {create: false, exclusive: false}, function(dirEntry){
          var directoryReader = dirEntry.createReader();
          directoryReader.readEntries(function(entries){
            var flist = [];

            for (var i = 0; i < entries.length; i++){
              flist.push(entries[i].name);
            }

            if (callback) {
              callback(flist);
            }
          }, fail);
        }, fail);
      }, fail);
    };

    fileSystem.exists = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      var dirName = fileSystem.extractDirectory(path);
      var fileName = fileSystem.extractFilename(path);

      var success = function(flist){
        for (var i = 0; i < flist.length; i++){
          if (flist[i].match(fileName)){
            if (callback) {
              callback(true);
            }

            return;
          }
        }

        if (callback) {
          callback(false);
        }
      };

      var error = function(){
        if (callback) {
          callback(false);
        }
      };

      fileSystem.readdir(dirName, success, error);
    };

    fileSystem.rmdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      var cleanup = function(directory, callback){
        if (directory.substring(directory.length - 1) === '/') directory = directory.substring(0, directory.length - 1);

        fileSystem.readdir(directory, function(entries){

          var checkEntry = function(index){
            if (entries[index]) {
              if (entries[index].indexOf('.') >= 0) {
                fileSystem.unlink(directory + '/' + entries[index], function(){
                  checkEntry(index + 1);
                });
              } else {
                cleanup(directory + '/' + entries[index], function(){
                  fileSystem.removeDirectory(directory + '/' + entries[index]);
                  checkEntry(index + 1);
                });
              }
            } else {
              if (callback) {
                callback();
              }
            }
          };

          checkEntry(0);

        });
      };

      fileSystem.getFileSystem(function(fs){
        cleanup(path, function(){
          fileSystem.removeDirectory(path, callback);
        });
      }, callback);
    };

    fileSystem.removeDirectory = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      fileSystem.getFileSystem(function(fs){
        fs.root.getDirectory(path, {create: false, exclusive: false}, function(dirEntry){
          dirEntry.remove(callback, callback);
        }, callback);
      }, callback);
    };

    fileSystem.mkdir = function(path, mode, callback, error){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      if (! error) {
        error = callback;
      }

      var folders = path.split('/');
      var folderIndex = 0;

      var createFolder = function(dirEntry){
        var path = folders[folderIndex];

        if (path && path.length > 0) {
          dirEntry.getDirectory(path, {create: true, exclusive: false}, createdFolder, error);
        } else {
          if (callback) {
            callback(dirEntry);
          }
        }
      };

      var createdFolder = function(dirEntry){
        folderIndex++;
        createFolder(dirEntry);
      };

      fileSystem.getFileSystem(function(fs){
        createFolder(fs.root);
      }, callback);
    };

    fileSystem.unlink = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      if (path.indexOf('.') >= 0) {
        fileSystem.getFileSystem(function(fs){
          fs.root.getDirectory(fileSystem.extractDirectory(path), {create: false, exclusive: false}, function(dirEntry){
            dirEntry.getFile(fileSystem.extractFilename(path), {create: false}, function(fileEntry){
              fileEntry.remove(callback, callback);
            }, callback);
          }, callback);
        }, callback);
      } else {
        fileSystem.rmdir(path, callback);
      }
    };

    fileSystem.rename = function(path, newPath, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);
      newPath = fileSystem.correctLocalFilePath(newPath);

      var move = function(entry, toDirectory, withFilename){
        entry.moveTo(toDirectory, withFilename, callback, callback);
      };

      fileSystem.getFileSystem(function(fs){
        fileSystem.mkdir(fileSystem.extractDirectory(path), 0744, function(dirEntry){
          fileSystem.mkdir(fileSystem.extractDirectory(newPath), 0744, function(dirEntryNew){
            // if it is a file
            if (path.indexOf('.') >= 0) {
              dirEntry.getFile(fileSystem.extractFilename(path), {create: false}, function(entry){
                move(entry, dirEntryNew, fileSystem.extractFilename(newPath));
              });
            } else {
              dirEntry.getDirectory(fileSystem.extractFilename(path), {create: false}, function(entry){
                move(entry, dirEntryNew, fileSystem.extractFilename(newPath));
              });
            }
          });
        });
      }, callback);
    };

    fileSystem.checksum = function(path, callback){
      // correct file path
      path = fileSystem.getLocalFilePath(path);

      var _calledCallback = null;
      var _autoFailTimeout = null;

      var done = function(data){
        if (_calledCallback) return;
        _calledCallback = true;

        if (_autoFailTimeout) clearTimeout(_autoFailTimeout);

        if (callback) {
          callback(data);
        }
      };

      var fail = function(){
        if (_calledCallback) return;
        done('');
      };

      md5chksum.file(path, function(sum) {
        if (sum && sum.length > 0) {
          done(sum);
        } else {
          fail();
        }
      }, fail);

      _autoFailTimeout = setTimeout(fail, 100);
    };

    fileSystem.init = function(callback){
      fileSystem.getFileSystem(function(fs){
        fileSystem.dataBasePath = fs.root.nativeURL.replace('file://', '');
        fileSystem.dataPath = fileSystem.dataBasePath + fileSystem.dataSubFolder + '/';

        if (config.debug) {
          console.log(fileSystem.dataBasePath);
        }

        if (callback) {
          callback();
        }
      });
    };

    var _cachedFileSystem = null;

    fileSystem.getFileSystem = function(callback){
      if (_cachedFileSystem) {
        if (callback) {
          callback(_cachedFileSystem);
        }
      } else {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs){
          _cachedFileSystem = fs;

          if (callback) {
            callback(fs);
          }
        });
      }
    };

    fileSystem.extractDirectory = function(path){
      var dirPath;
      var lastSlash = path.lastIndexOf('/');

      if (lastSlash === -1) {
        dirPath = '/';
      } else {
        dirPath = path.substring(0, lastSlash);

        if (dirPath === '') {
          dirPath = '/';
        }
      }

      return dirPath;
    };

    fileSystem.extractFilename = function(path){
      var lastSlash = path.lastIndexOf('/');

      if (lastSlash === -1){
        return path;
      }

      var filename =  path.substring(lastSlash + 1);

      return filename;
    };
  };
});
<|endoftext|>