2
\$\begingroup\$

could some review my lightbox plugin.

I have created a codepen here for you to see the code in action. It started out pretty neat but I think the code get slightly messy, just looking for some general advice on how to improve upon this next time and maybe in the next version.

It has got a couple of features:

  • Keyboard Navigation Support
  • Gallery to view all images.
  • Multiple Instances of the Lightbox for multiple image groups.
  • Opened some functions and variables to be accessed from outside, so slides can be changed from external sources for example: lightbox[0].publicMethods('slide', 4)

The JavaScript:

// Polyfill Functions //
(function () {
  "use strict";
  if (!Object.observe && typeof (Proxy) === "function") {
    function Observer(target, callback, acceptlist, pausable, pause, delay) {
      var me = this,
        proxy;

      function deliver(ignorePrevious, delay) {
        deliver.delay = delay;
        if (!deliver.pause) {
          if (me.changeset.length > 0) {
            if (!ignorePrevious) {
              var changes = me.changeset.filter(function (change) {
                return !acceptlist || acceptlist.indexOf(change.type) >= 0;
              });
              if (changes.length > 0) {
                callback(changes);
              }
            }
            me.changeset = [];
          }
        }
      }
      deliver.pause = pause;
      deliver.delay = delay;
      me.get = function (target, property) {
        if (property === "__observer__") {
          return me;
        }
        if (property === "unobserve") {
          return function () {
            Object.unobserve(target);
            return target;
          };
        }
        if (property === "deliver") {
          return deliver;
        }
        return target[property];
      }
      me.target = target;
      me.changeset = [];
      if (!me.target.__observerCallbacks__) {
        // __observerCallbacks__ is used as an index to get at the proxy which is the observer, so we can unobserve
        Object.defineProperty(target, "__observerCallbacks__", {
          enumerable: false,
          configurable: true,
          writable: false,
          value: []
        });
        Object.defineProperty(target, "__observers__", {
          enumerable: false,
          configurable: true,
          writable: false,
          value: []
        });
      }
      me.target.__observerCallbacks__.push(callback);
      me.target.__observers__.push(this);
      proxy = new Proxy(target, me);
      deliver(false, delay);
      return proxy;
    }
    Observer.prototype.deliver = function () {
      return this.get(null, "deliver");
    }
    Observer.prototype.set = function (target, property, value) { // , receiver
      var oldvalue = target[property];
      var type = (oldvalue === undefined ? "add" : "update");
      target[property] = value;
      if (target.__observers__.indexOf(this) >= 0 && (!this.acceptlist || this.acceptlist.indexOf(type) >= 0)) {
        var change = {
            object: target,
            name: property,
            type: type
          },
          start = this.changeset.length === 0,
          deliver = this.deliver();
        if (type === "update") {
          change.oldValue = oldvalue;
        }
        this.changeset.push(change);
        if (start) {
          deliver(false, (typeof (deliver.delay) === "number" ? deliver.delay : 10));
        }
      }
      return true;
    };
    Observer.prototype.deleteProperty = function (target, property) {
      var oldvalue = target[property];
      //if(typeof(oldvalue)!=="undefined") {
      delete target[property];
      if (target.__observers__.indexOf(this) >= 0 && !this.acceptlist || this.acceptlist.indexOf("delete") >= 0) {
        var change = {
            object: target,
            name: property,
            type: "delete",
            oldValue: oldvalue
          },
          start = this.changeset.length === 0,
          deliver = this.deliver();
        this.changeset.push(change);
        if (start) {
          deliver(false, (typeof (deliver.delay) === "number" ? deliver.delay : 10));
        }
      }
      //}
      return true;
    };
    Observer.prototype.defineProperty = function (target, property, descriptor) {
      Object.defineProperty(target, property, descriptor);
      if (target.__observers__.indexOf(this) >= 0 && !this.acceptlist || this.acceptlist.indexOf("reconfigure") >= 0) {
        var change = {
            object: target,
            name: property,
            type: "reconfigure"
          },
          start = this.changeset.length === 0,
          deliver = this.deliver();
        this.changeset.push(change);
        if (start) {
          deliver(false, (typeof (deliver.delay) === "number" ? deliver.delay : 10));
        }
      }
      return true;
    };
    Observer.prototype.setPrototypeOf = function (target, prototype) {
      var oldvalue = Object.getPrototypeOf(target);
      Object.setPrototypeOf(target, prototype);
      if (target.__observers__.indexOf(this) >= 0 && !this.acceptlist || this.acceptlist.indexOf("setPrototype") >= 0) {
        var change = {
            object: target,
            name: "__proto__",
            type: "setPrototype",
            oldValue: oldvalue
          },
          start = this.changeset.length === 0,
          deliver = this.deliver();
        this.changeset.push(change);
        if (start) {
          deliver(false, (typeof (deliver.delay) === "number" ? deliver.delay : 10));
        }
      }
      return true;
    };
    Observer.prototype.preventExtensions = function (target) {
      Object.preventExtensions(target);
      if (target.__observers__.indexOf(this) >= 0 && !this.acceptlist || this.acceptlist.indexOf("preventExtensions") >= 0) {
        var change = {
            object: target,
            type: "preventExtensions"
          },
          start = this.changeset.length === 0,
          deliver = this.deliver();
        this.changeset.push(change);
        if (start) {
          deliver(false, (typeof (deliver.delay) === "number" ? deliver.delay : 10));
        }
      }
      return true;
    };
    Object.observe = function (object, callback, acceptlist, pausable, pause, delay) {
      return new Observer(object, callback, acceptlist, pausable, pause, delay);
    };
    Object.unobserve = function (object, callback) {
      if (object.__observerCallbacks__) {
        if (!callback) {
          object.__observerCallbacks__.splice(0, object.__observerCallbacks__.length);
          object.__observers__.splice(0, object.__observers__.length);
          return;
        }
        object.__observerCallbacks__.forEach(function (observercallback, i) {
          if (callback === observercallback) {
            object.__observerCallbacks__.splice(i, 1);
            delete object.__observers__[i].callback;
            object.__observers__.splice(i, 1);
          }
        });
      }
    };
    Array.observe = function (object, callback, acceptlist, pausable, pause, delay) {
      if (!(object instanceof Array) && !Array.isArray(object)) {
        throw new TypeError("First argument to Array.observer is not an Array");
      }
      acceptlist = acceptlist || ["add", "update", "delete", "splice"];
      var arrayproxy = new Proxy(object, {
        get: function (target, property) {
          if (property === "unobserve") {
            return function (callback) {
              if (callback) {
                return Object.unobserve(target, callback);
              }
              return target.unobserve();
            };
          }
          if (property === "splice") {
            return function (start, end) {
              if (typeof (start) !== "number" || typeof (end) !== "number") {
                throw new TypeError("First two arguments to Array splice are not number, number");
              }
              var removed = this.slice(start, start + end),
                addedCount = (arguments.length > 1 ? arguments.length - 2 : 0),
                change = {
                  object: object,
                  type: "splice",
                  index: start,
                  removed: removed,
                  addedCount: addedCount
                };
              target.splice.apply(target, arguments);
              if (acceptlist.indexOf("splice") >= 0) {
                var start = proxy.__observer__.changeset.length === 0,
                  deliver = proxy.__observer__.deliver();
                proxy.__observer__.changeset.push(change);
                if (start) {
                  deliver(false, (typeof (deliver.delay) === "number" ? deliver.delay : 10));
                }
              }
            }
          }
          if (property === "push") {
            return function (item) {
              return this.splice(this.length, 0, item);
            }
          }
          if (property === "pop") {
            return function () {
              return this.splice(this.length - 1, 1);
            }
          }
          if (property === "unshift") {
            return function (item) {
              return this.splice(0, 0, item);
            }
          }
          if (property === "shift") {
            return function () {
              return this.splice(0, 1);
            }
          }
          return target[property];
        }
      });
      var proxy = Object.observe(arrayproxy, function (changeset) {
        var changes = changeset.filter(function (change) {
          return change.name !== "length" && change.name !== "add" && (!acceptlist || acceptlist.indexOf(change.type) >= 0);
        });
        if (changes.length > 0) {
          callback(changes);
        }
      }, acceptlist, pausable, pause, delay);
      return proxy;
    };
    Array.unobserve = function (object, callback) {
      return object.unobserve(callback);
    }
  }
  Object.deepObserve = function (object, callback, parts) {

    parts = (parts ? parts : []);

    var toTypeName = function (obj) {
      return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
    }

    function reobserve(value, parts) {
      var keys = Object.keys(value);
      keys.forEach(function (key) {
        if ((toTypeName(value[key]) === 'object' || toTypeName(value[key]) === 'array') && !value[key].hasOwnProperty('__observers__')) {
          var newparts = parts.slice(0);
          newparts.push(key);
          value[key] = Object.deepObserve(value[key], callback, newparts);
        }
      });
    }

    reobserve(object, parts);

    var observed = Object.observe(object, function (changeset) {
      var changes = [];

      function recurse(name, rootObject, oldObject, newObject, path) {
        if (newObject instanceof Object) {
          var newkeys = Object.keys(newObject);
          newkeys.forEach(function (key) {
            if (!oldObject || (oldObject[key] !== newObject[key])) {
              var oldvalue = (oldObject && oldObject[key] !== undefined ? oldObject[key] : undefined),
                change = (oldvalue === undefined ? "add" : "update"),
                keypath = path + "." + key;

              changes.push({
                name: name,
                object: rootObject,
                type: change,
                oldValue: oldvalue,
                newValue: newObject[key],
                keypath: keypath
              });
              recurse(name, rootObject, oldvalue, newObject[key], keypath);
            }
          });
        } else if (oldObject instanceof Object) {
          var oldkeys = Object.keys(oldObject);
          oldkeys.forEach(function (key) {
            var change = (newObject === null ? "update" : "delete"),
              keypath = path + "." + key;

            changes.push({
              name: name,
              object: rootObject,
              type: change,
              oldValue: oldObject[key],
              newValue: newObject,
              keypath: keypath
            });
            recurse(name, rootObject, oldObject[key], undefined, keypath);
          });
        }
      }
      changeset.forEach(function (change) {
        var keypath = (parts.length > 0 ? parts.join(".") + "." : "") + change.name;

        if (change.type === "update" || change.type === "add") {
          reobserve(change.object, parts);
        }

        changes.push({
          name: change.name,
          object: change.object,
          type: change.type,
          oldValue: change.oldValue,
          newValue: change.object[change.name],
          keypath: keypath
        });
        recurse(change.name, change.object, change.oldValue, change.object[change.name], keypath);
      });
      callback(changes);
    });
    return observed;
  };
})();
// END Poly Functions //

// Custom Prototypes
String.prototype.objectType = function () {
  var t = null,
    type;
  switch (this.charAt(0)) {
    case '#':
      type = 'id';
      t = document.getElementById(this.replace('#', ''));
      break;
    case '.':
      type = 'class';
      t = document.getElementsByClassName(this.replace('.', ''))[0];
      break;
    default:
      type = 'class';
      t = document.getElementsByClassName(this.replace('.', ''))[0];
      break;
  }
  return t;
};
HTMLElement.prototype.offset = function () {
  var _ = this;

  var left = 0,
    right = 0,
    top = 0,
    bottom = 0;

  do {
    if (!isNaN(_.offsetLeft)) {
      left += _.offsetLeft;
    }
  } while (_ = _.offsetParent);
  _ = this;

  do {
    if (!isNaN(_.offsetTop)) {
      top += _.offsetTop;
    }
  } while (_ = _.offsetParent);
  _ = this;

  do {
    if (!isNaN(_.offsetRight)) {
      right += _.offsetRight;
    }
  } while (_ = _.offsetParent);
  _ = this;

  do {
    if (!isNaN(_.offsetBottom)) {
      bottom += _.offsetBottom;
    }
  } while (_ = _.offsetParent);
  _ = this;

  return {
    left: left,
    top: top,
    right: right,
    bottom: bottom
  };
};
String.prototype.generateHTMLfromJSON = function () {
  var handleAttribute = function (element, attribute, value) {
    if (value instanceof HTMLElement) {
      return element.appendChild(value);
    }
    switch (attribute) {
      case 'class':
      case 'src':
      case 'id':
      case 'data-click':
      case 'data-hover':
      case 'data-name':
        element.setAttribute(attribute, value);
        return;
      case 'content':
        element.innerHTML = value;
        return;
      case 'watch':
      case 'unwatch':
        return;
      default:
        console.log(element.tagName, attribute, value);
    }
  };
  var htmlReviver = function (key, value) {
    // parse as element
    if (isNaN(key) && typeof value === 'object') {
      var element = document.createElement(key);
      var subValue;

      for (var attribute in value) {
        handleAttribute(element, attribute, value[attribute]);
      }

      return element;
    } else if (!isNaN(key)) {
      return value[Object.keys(value)[0]];
    } else {
      return value;
    }
  };
  var object;

  try {
    object = JSON.parse(this, htmlReviver);
  } catch (e) {
    return [ false, e];
  }
  return [true, object];
}
function whichAnimationEvent() {
  var t,
    el = document.createElement("fakeelement");

  var animations = {
    "animation": "animationend",
    "OAnimation": "oAnimationEnd",
    "MozAnimation": "animationend",
    "WebkitAnimation": "webkitAnimationEnd"
  }

  for (t in animations) {
    if (el.style[t] !== undefined) {
      return animations[t];
    }
  }
}
// END Custom Prototypes

// Plugin Start
class LightBox {

  constructor(node) {
    let _ = this;

    Object.assign(this, {
      node: node,
      label: node.dataset.lightboxName,
      group: node.dataset.lightboxGroup,
      enabled: true,
      id: 'lightbox',

      activeClass: 'lightboxActive',
      appendTo: '#main-wrap',
      debug: false,

      // Visual settings
      showTitle: true,
      showMax: true,
      showThumbs: true,

      lightboxHTML: '{ "div": { "id": "{name}", "0": { "div": { "class": "lightboxSlider", "data-name": "container", "0": { "div": { "class": "{name}Slider__header", "data-name": "header", "data-click": "EventClose", "0": { "div": { "class": "{name}Slider__counter", "data-name": "counter" } }, "1": { "div": { "class": "{name}Slider__title", "data-name": "title" } }, "2": { "div": { "class": "{name}Slider__close", "data-click": "EventClose", "data-name": "close" } } } }, "1": { "div": { "class": "{name}Slider__main", "data-click": "EventClose", "data-name": "main", "0": { "div": { "class": "{name}Slider__maincontrols {name}Slider__maincontrols--left", "data-click": "EventLeft", "data-name": "left" } }, "1": { "div": { "class": "{name}Slider__image", "data-name": "imageContainer", "0": { "div": { "class": "{name}Slider__imagecontrols {name}Slider__imagecontrols--left", "data-click": "EventLeft", "data-hover": "EventClass(#{name}, toggle: hover-left)", "data-name": "imageLeft" } }, "1": { "div": { "class": "{name}Slider__imagecontrols {name}Slider__imagecontrols--right", "data-click": "EventRight", "data-hover": "EventClass(#{name}, toggle: hover-right)", "data-name": "imageRight" } }, "2": { "img": { "src": "", "class": "responsive-img image", "data-name": "image" } } } }, "2": { "div": { "class": "{name}Slider__maincontrols {name}Slider__maincontrols--right", "data-click": "EventRight", "data-name": "right" } } } }, "2": { "div": { "class": "{name}Slider__bottom", "data-name": "bottom", "0": { "div": { "class": "{name}Slider__gallery", "data-click": "EventGallery" } } } } } } } }',

      thumbnailsHTML: '{ "div": { "class": "{name}Gallery", "data-name": "container", "0": { "div": { "class": "{name}Gallery__header", "data-name": "header", "0": { "div": { "class": "{name}Gallery__back", "data-name": "back", "content": "< Back" } }, "1": { "div": { "class": "{name}Gallery__title", "data-name": "title" } }, "2": { "div": { "class": "{name}Gallery__close", "data-click": "EventClose", "data-name": "close" } } } }, "1": { "div": { "class": "{name}Gallery__main", "data-click": "EventClose", "data-name": "main" } } } }',

      galleryHTML: '{ "div": { "class": "{name}Gallery", "data-name": "container", "0": { "div": { "class": "{name}Gallery__header", "data-name": "header", "data-click": "EventClose", "0": { "div": { "class": "{name}Gallery__back", "data-name": "back", "data-click": "EventGalleryClose", "content": "Back" } }, "1": { "div": { "class": "{name}Gallery__title", "data-name": "title", "content": "{group}" } }, "2": { "div": { "class": "{name}Gallery__close", "data-click": "EventClose", "data-name": "close" } } } }, "1": { "div": { "class": "{name}Gallery__main", "data-click": "EventClose", "data-name": "main" } } } }'

    });

    // We want to extend this class to add the thumbnails at some point

    // Store variables
    _.count = -1; // Count images
    _.imageList = null; // Save list of images to use in lightbox
    _.active = false; // Is lightbox active?

    _.current = null; // Current image index
    _.last = null; // Last image index

    // Replace bracelets
    _.lightboxHTML = _.lightboxHTML.replace(/\{(name)}/g, _.id);
    _.thumbnailsHTML = _.thumbnailsHTML.replace(/\{(name)}/g, _.id);
    _.galleryHTML = _.galleryHTML.replace(/\{(name)}/g, _.id);

    _.galleryHTML = _.galleryHTML.replace(/\{(group)}/g, _.label);

    // Errors occured?
    _.error = false;

    // Before we do anything, let's compile an Array of objects
    // containing information about each image.

    // We will use the _.group value to only get the images within this instance of the lightbox
    _.images = document.querySelectorAll('[data-lightbox-group=' + _.group + ']');

    // Now add the objects to the images array
    _.images = Array.from(_.images, function (elm) {
      _.count++; // Increase max number for each image
      elm.classList.add('addedLightbox'); // Add some reference to the image that it has been added, styling?
      return {
        index: _.count, // We want a unique index for each image
        object: elm, // Store a reference to the image

        // Establish if a different image should be used when viewed in the lightbox
        src: function () {
          if (elm.hasAttribute('data-lightbox-large')) return elm.getAttribute('data-lightbox-large');
          return elm.src;
        }(),

        // Store an active value, be nice to add an event listener to
        // this to add/remove classes
        active: {
          val: 0
        },
        alt: function() {
          if (elm.hasAttribute('alt')) return elm.getAttribute('alt');
          return '';
        }()
      };
    });

    // Lets start to setup the lightbox, only continue if no fatal errors occured

    if (!this.error) this.initEvents.call(this); // Setup object with lightbox update functions
    if (!this.error) this.initHTML.call(this); // Generate the HTML for the lightbox
    if (!this.error) this.initUpdate.call(this, _.images); // Assign the click events to images, pass in any list
    if (!this.error) this.initThumbnails.call(this, _.images);
    if (!this.error) this.initKeyboard.call(this); // Assign events to keyboard events
    if (!this.error) this.initTouch.call(this); // Assign events to touch screen events

    /**
     *  Public Methods
     */
    _.publicMethods = function() {
      var a = arguments[0], b = arguments[1];
      switch(a) {
        // Actions
        case 'left':
          _.events.EventLeft();
        break;
        case 'right':
          _.events.EventRight();
        break;
        case 'slide':
          _.events.EventSlide((b - 1));
        break;
        case 'close':
          _.events.EventClose();
        break;

        // Return Values
        case 'get': return _.events.ReturnVal(b);
      }
    }
  }

  /**
   * Initiate lightbox for all groups found.
   */
  static init(node) {
    return new this(node);
  }
  static initAll(node) {
    let _ = this, instances = [];

    window.addEventListener('load', function() {
      var groups = document.querySelectorAll('[data-lightbox-group]');
      var current = [];
      var handleGroup = function (e) {
        instances.push(_.init(e)); // Setup new instance of this class for each group
      };

      // Establish unique group names and create new instances
      for (var i = 0; i < groups.length; i++) {
        var dataset = groups[i].dataset;
        if (current.indexOf(dataset.lightboxGroup) === -1) {
          current.push(dataset.lightboxGroup);
          handleGroup(groups[i]);
        }
      }
    });

    return instances;
  }

  /**
   *  Handle plugin for each group
   */
  initEvents() {
    var _ = this;

    _.events = {
      // Go to next image
      EventRight: function (evt) {
        if (!_.galleryActive) {
          var newIndex = 0;
          if (evt) evt.stopPropagation();

          if (_.current < _.count) newIndex = (_.current + 1);

          _.updateLightbox.call(_, newIndex); // Update lightbox

          // Update active classes
          _.images[_.current].active.val = 1;
          _.images[_.last].active.val = 0;
        }
      },
      // Go to previous image
      EventLeft: function (evt) {
        if (!_.galleryActive) {
          var newIndex = _.count;
          if (evt) evt.stopPropagation();

          if (_.current >= 1) newIndex = _.current - 1;

          _.updateLightbox.call(_, newIndex); // Update lightbox

          // Update active classes
          _.images[newIndex].active.val = 1;
          _.images[_.last].active.val = 0;
        }
      },
      // Close the lightbox
      EventClose: function (evt) {
        if (evt) evt.stopPropagation();

        _.updateLightbox.call(_, null);

        // Make sure all active classes are removed
        for (var i = 0; i < _.images.length; i++) {
          _.images[i].active.val = 0;
        }
      },
      // Switch the slider number
      EventSlide: function (number) {
        if (number instanceof Object) {
          number.stopPropagation();
          number = ((number.target.dataset.click).match(/[^(]*/g).filter(Boolean))[1].match(/(?:([^,()]+)?)+/g).filter(Boolean);
        }
        if (_.images[number] !== undefined) {
          _.updateLightbox.call(_, number); // Go to slide

          _.images[_.last].active.val = 0;
          _.images[number].active.val = 1;
        } else {
          // No such image...
        }
      },
      EventClass: function (evt) {
        try {
          var arg = (evt.target.dataset.hover).replace(/ /g, ''),
            args = (arg.match(/[^(]*/g).filter(Boolean))[1].match(/(?:([^,()]+)?)+/g).filter(Boolean),
            target = args[0].objectType(),
            action = args[1].match(/^(.*):(.*)/).filter(Boolean);

          switch (action[1]) {
            case 'add':
              // Add class
              target.classList.add(action[2]);
              break;
            case 'remove':
              // Remove class
              target.classList.remove(action[2]);
              break;
            case 'toggle':
              target.classList.add(action[2]);
              evt.target.addEventListener('mouseout', function () {
                target.classList.remove(action[2]);
              });
              break;
            default:
              // action doesn't exist, log error
              break;
          }
        } catch (e) {
          // error
        }
      },
      ReturnVal: function (e) {
        var val;
        switch(e) {
          case 'count':
            val = _.count;
          break;
          case 'active':
            val = _.active;
          break;
          case 'current':
            val = _.current;
          break;
          case 'last':
            val = _.last;
          break;
          case 'images':
            val = _.images;
          break;
          case 'html':
            val = _.lightbox;
          break;
          default:
            val = _;
          break;
        }
        return val;
      },
      EventGallery: function() {
        _.galleryActive = true;
        _.gallery = _.HTMLfromJSON(_.galleryHTML);

        // Should duplicate the image reference, but keep reference to the active state
        var newList = [];
        var createImage = function(image, i) {

          // Create new instances of the images
          var img = document.createElement('img');
          img.src = image.object.src;
          img.classList.add('responsive-img');
          img.alt = image.alt;

          var span = document.createElement('span');
          span.appendChild(img);

          if (i === _.current) img.parentNode.classList.add('active');

          var newImage = {
            object: img,
            index: i,
            src: image.src,
            active: image.active, // Use reference to old list
            alt: image.alt
          };
          newList.push(newImage);

          newImage.object.addEventListener('click', function(evt) {

            evt.stopPropagation();

            // Change image
            _.updateLightbox.call(_, newImage.index);

            _.galleryActive = false;

            // Remove the gallery
            (_.gallery.object).parentNode.removeChild(_.gallery.object);

          });

          // Add each image to the gallery DOM
          _.gallery.main.appendChild(span);
        }

        for (var i = 0; i < _.images.length; i++) {
          createImage(_.images[i], i);
        }

        // Add the gallery to the DOM
        _.lightbox.object.appendChild(_.gallery.object);

      },
      EventGalleryClose: function(evt) {
        evt.stopPropagation();
        (_.gallery.object).parentNode.removeChild(_.gallery.object);
        _.galleryActive = false;
      }
    };
  }
  HTMLfromJSON(html) {
    let _ = this,
      result;

    // Filter the event name, this is in the data attribute value
    var eventName = function (e) {
      return e.match(/[^(]*/);
    };

    try {
      // Try and generate the HTML from the JSON
      result = html.generateHTMLfromJSON();
    } catch (e) {
      console.log('Lightbox Error (Generating HTML): ' + e);
    }

    // Check that the HTML was generated
    if (result[0]) {

      // Assigned mouse events to the HTML based on data attributes, 
      // this will assign the lightbox events functions.
      Array.prototype.forEach.call(result[1].querySelectorAll('[data-hover]'), function (e) {
        e.addEventListener('mouseover', function (event) {
          _.events[eventName(e.dataset.hover)](event);
        });
      });
      Array.prototype.forEach.call(result[1].querySelectorAll('[data-click]'), function (e) {
        e.addEventListener('click', function (event) {
          _.events[eventName(e.dataset.click)](event);
        });
      });

      // Store a reference to key elements with the HTML and
      // store the main HTML object, allow access to this references to
      // extended classes for updates and bolt-ons. 
      var object = {};
      object['object'] = result[1];
      Array.prototype.forEach.call(result[1].querySelectorAll('[data-name]'), function (e) {
        object[e.dataset.name] = e;
      });

    } else {
      _.error = true; // HTML didn't generate
      console.log('error generating html: ' + result[1]);
    }

    // Return the HTML
    return object;
  }
  initHTML() {
    let _ = this;

    // Use JSON to generate the HTML, this allows the HTML structure to be changes
    _.lightbox = _.HTMLfromJSON(_.lightboxHTML);

    // Generate thumbnail code?
    if (_.showThumbs) {
      _.thumbnails = _.HTMLfromJSON(_.thumbnailsHTML); // Generate the HTML

      // Create a div to wrap the images in
      let wrapper = document.createElement('div');
      wrapper.classList.add('thumbnails');

      var newList = [];
      _.imagesThumbnails = [];
      for (var i = 0; i < _.images.length; i++) {
        var img = document.createElement('img');
        img.src = _.images[i].object.src;
        img.alt = _.images[i].alt;

        newList.push({
          object: img,
          index: i,
          src: _.images[i].src,
          active: _.images[i].active, // Use reference to old list
          alt: _.images[i].alt
        });

        let container = document.createElement('span');
        container.appendChild(newList[i].object);

        // Keep a reference to the thumbnails
        _.imagesThumbnails.push(newList[i]);

        // Add click events to thumbnails
        container.addEventListener('click', function(i) {
          _.events.EventSlide(newList[i].index);
        }.bind(_, i));

        // Append image to wrapper
        wrapper.appendChild(container);
      }

      _.lightbox.bottom.appendChild(wrapper);
      _.lightbox.thumbnailWrapper = wrapper;
    }

    // If there is only 1 image add a class, some HTMLElement's may need to be hidden.
    if (_.images.length <= 1) {
      _.lightbox.container.classList.add(_.id + 'Slider--hide-controls');
    }
  }
  initUpdate(list /* list = { index, object, src, active } */ ) {
    let _ = this;

    // Assign click events for objects within the list
    try {
      for (var i = 0; i < list.length; i++) {
        let image = list[i];
        let thumb = (_.imagesThumbnails) ? _.imagesThumbnails[i] : false;

        // Watch for active state to change, then add class
        // Mainly for visual feedback which image is active
        image.active = Object.observe(image.active, function (changeset) {
          if (changeset[0].name === 'val') {
            if (image.active.val === 1) {

              // Image changed, change lightbox contents
              _.lightbox.counter.innerHTML = (_.current + 1) + '/' + _.images.length;
              _.lightbox.image.src = _.images[_.current].src;
              _.lightbox.image.alt = _.images[_.current].alt;
              _.lightbox.title.innerHTML = _.images[_.current].alt;

              // Add classes to thumbnails
              image.object.classList.add('active');
              if (thumb) thumb.object.parentNode.classList.add('active');

            } else {
              image.object.classList.remove('active');
              if (thumb) thumb.object.parentNode.classList.remove('active');
            }
          }
        });

        // When image is clicked we need to update the lightbox
        // This could be initiated from bolt-on class
        (image.object).addEventListener('click', function (event) {

          event.stopPropagation(); // Incase image is within an <a> tag

          // Update current item, send to external handler
          _.updateLightbox.call(_, image.index);

        });
      }
    } catch (e) {
      // Fatal error
      _.error = true;
      console.log('Error: ' + e);
    }
  }
  initThumbnails(list /* list = { index, object, src, active } */ ) {

    // Should duplicate the image reference, but keep reference to the active state
    var newList = [];
    for (var i = 0; i < list.length; i++) {
      var img = document.createElement('img');
      img.src = list[i].object.src;

      newList.push({
        object: img,
        index: i,
        src: list[i].src,
        active: list[i].active // Use reference to old list
      });
    }
  }
  updateLightbox(index) {
    var _ = this;

    // If null is passed then close the lightbox
    if (index === null) {

      var transitionEvent = whichAnimationEvent();

      _.lightbox.object.classList.add("remove");
      _.lightbox.object.addEventListener(transitionEvent, removeLightbox);

      function removeLightbox(event) {

        _.lightbox.object.removeEventListener(transitionEvent, removeLightbox);
        _.lightbox.object.classList.remove("remove");
        if (_.lightbox.object) _.lightbox.object.parentNode.removeChild(_.lightbox.object);
        document.body.classList.remove(_.activeClass);

        _.active = false;
        _.current = null;
      }

    } else {
      /* images = [ { index, object, src, active } ] */

      // Change lightbox image
      if (_.images[index] !== undefined) {
        try {

          // Update lightbox
          _.last = (_.current === null) ? index : _.current;
          _.current = index;
          _.active = true;

          // Append the HTML if it doesn't yet exist
          if (('#lightbox').objectType() === null) {
            (_.appendTo).objectType().insertBefore(_.lightbox.object, ((_.appendTo).objectType()).firstChild);
            document.body.classList.add(_.activeClass);
          }

          // Change the active image
          _.images[_.last].active.val = 0;
          _.images[index].active.val = 1;
          if (_.showThumbs) _.updateThumbnailPadding.call(_, _.current);

        } catch (e) {
          _.error = true;
          console.log(e);
        }
      } else {
        // Image doesn't exist
      }
    }
  }
  initKeyboard() {
    var _ = this;

    document.addEventListener('keydown', function (e) {
      if (_.active) {
        e = e || window.event;
        if (e.keyCode === 37 || e.keyCode === 39 || e.keyCode === 27) {
          switch (e.keyCode) {
            case 39:
              _.events.EventRight(); // Right
              break;
            case 37:
              _.events.EventLeft(); // Left
              break;
            case 27:
              _.events.EventClose(); // Close
              break;
          }
        }
      }
    });
  }
  initTouch() {
    var _ = this;

    var xDown = null,
      yDown = null;

    document.addEventListener('touchstart', function (evt) {
      xDown = evt.touches[0].clientX;
      yDown = evt.touches[0].clientY;
    }, false);

    document.addEventListener('touchmove', function (evt) {
      if (_.current !== null) {
        if (!xDown || !yDown) {
          return;
        }

        var xUp = evt.touches[0].clientX;
        var yUp = evt.touches[0].clientY;

        var xDiff = xDown - xUp;
        var yDiff = yDown - yUp;

        if (Math.abs(xDiff) > Math.abs(yDiff)) {
          if (xDiff > 0) {
            _.events.EventRight(); // Right
          } else {
            _.events.EventLeft(); // Left
          }
        }

        /* reset values */
        xDown = null;
        yDown = null;
      }
    });
  }
  updateThumbnailPadding(index) {
    var _ = this;
    var pageWidth = document.body.offsetWidth / 2,
        elementWidth = (_.imagesThumbnails[index].object.parentNode.offsetWidth / 2),
        offsetLeft = _.imagesThumbnails[index].object.parentNode.offsetLeft;

    var offset = (pageWidth - _.imagesThumbnails[index].object.offset().left) - elementWidth;

    (_.lightbox.thumbnailWrapper).style.transform = 'translateX(' + offset + 'px)';
  }
}
var lightbox = LightBox.initAll();

Example HTML you can use:

<html>
  <head></head>
  <body>
    <div id="main-wrap">
      <div class="html1">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-16.jpg" alt="Image 1" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-1.jpg" alt="Image 2" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-3.jpg" alt="Image 3" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-16.jpg" alt="Image 1" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-1.jpg" alt="Image 2" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-3.jpg" alt="Image 3" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-16.jpg" alt="Image 1" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-1.jpg" alt="Image 2" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-3.jpg" alt="Image 3" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-16.jpg" alt="Image 1" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-1.jpg" alt="Image 2" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
        <img src="https://mgreviewsblog.files.wordpress.com/2014/08/2014-ducati-899-panigale-white-3.jpg" alt="Image 3" class="responsive-img" data-lightbox-group="detail_gallery" data-lightbox-name="Gallery">
      </div>
    </div>
  </body>
</html>
\$\endgroup\$
2
  • 1
    \$\begingroup\$ This is 1000 lines of code -- too long to expect good answers or thorough answers. You should consider extracting out relevant pieces into self-contained posts. \$\endgroup\$
    – Jonah
    Jul 10 '17 at 20:29
  • \$\begingroup\$ @Jonah ah okay, fair enough. \$\endgroup\$ Jul 11 '17 at 8:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.