1


I am trying to create one gallery class using Javascript and jQuery.

I have some prototype function for that gallery class, but when I try to access those functions inside class it shows error.

Uncaught TypeError: Gallery.start is not a function

My JSfiddle Link

Here is my Javascript:

/**
 *
 * @param options should be object of following
 * options.images Array of gallery images URL
 * options.start slide starting point
 * options.autoPlay This option will be false by default
 *
 */
function Gallery(options) {
  var _this = this;
  var galleryOptions = options;

  function randomString(len, charSet) {
    var ranString = '';
    var randomPoz;
    charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (var i = 0; i < len; i++) {
      randomPoz = Math.floor(Math.random() * charSet.length);
      ranString = ranString + charSet.substring(randomPoz, randomPoz + 1);
    }
    return ranString;
  }

  function addThumbnail(imgSrc) {
    return '<li class="thumbnail__list"><img src="' + imgSrc + '" class="thumbnail__list-img" /></li>';
  }

  function renderThumbnail(imgArr) {
    var html = [];
    if (imgArr) {
      $.each(imgArr, function(i, v) {
        html.push(addThumbnail(v));
      });
    }
    return '<ul class="gallery__thumbnail__container">' + html.join('') + '</ul>';
  }

  function getPlaceholder() {
    return 'dummy.jpg';
  }

  function disableNext(thisObj) {
    thisObj.galleryElement.find('.next').addClass('disabled');
  }

  function disablePrev(thisObj) {
    thisObj.galleryElement.find('.prev').addClass('disabled');
  }

  function enableNext(thisObj) {
    thisObj.galleryElement.find('.next').removeClass('disabled');
  }

  function enablePrev(thisObj) {
    thisObj.galleryElement.find('.prev').removeClass('disabled');
  }

  function togglePrevNext(self) {
    if (self._opt.currIndex === (self._opt.imagesLength)) {
      disableNext(self);
    } else {
      enableNext(self);
    }
    if ((self._opt.currIndex - 1) === 0) {
      disablePrev(self);
    } else {
      enablePrev(self);
    }
  }

  function controls() {
    var html = [];
    html.push('<div class="prev sz-icon-arrow-left"></div>');
    html.push('<div class="next sz-icon-arrow-right"></div>');
    return '<div class="gallery__controls">' + html.join('') + '</div>';
  }

  function bindClickEvents(galleryElement) {
    galleryElement.on('click', '.start', function() {
      _this.start();
    });
    galleryElement.find('.stop').on('click', function() {
      _this.stop();
    });
    galleryElement.find('.prev').on('click', function() {
      _this.prev();
    });
    galleryElement.find('.next').on('click', function() {
      _this.next();
    });
    galleryElement.find('.thumbnail__list').on('click', function() {
      _this.goTo(Number($(this).index()) + 1);
    });
  }

  function checkOptions(option) {
    var opt = option;
    opt.images = (opt.images.length) ? opt.images : getPlaceholder();
    opt.thumbnail = (opt.thumbnail) ? true : false;
    opt.fullScreen = (opt.fullScreen) ? true : false;
    opt.container = (opt.fullScreen) ? 'body' : opt.container;
    opt.container = (opt.container) ? opt.container : 'body';
    opt.autoPlay = (opt.autoPlay) ? true : false;
    opt.start = (opt.start && opt.start <= Number(opt.images.length)) ? opt.start : 1;
    return opt;
  }

  Gallery.init = function() {
    var html;
    _this._opt = checkOptions(galleryOptions);
    _this._opt.imagesLength = Number(_this._opt.images.length);
    _this._opt.currIndex = Number(_this._opt.start);
    _this._opt.elementName = 'gallery--' + randomString(5, 'SZgallery');
    if (_this._opt.fullScreen) {
      html = '<div class="pop-up__model ' + _this._opt.elementName + '">' +
        '<div class="pop-up__model-table">' +
        '<div class="pop-up__model-cell">' +
        '<div class="gallery"><div class="pop-up__model-content">' +
        '<div class="gallery__inner-wrapper"></div>' +
        '</div></div>' +
        '</div></div>' +
        '</div>';
      $(_this._opt.container).append('', html);
      if (_this._opt.thumbnail) {
        $('.pop-up__model-content').append('', '<div class="thumbnail__list-wrapper">' + renderThumbnail(options.images) + '</div>').append('', controls());
      }
    } else {
      $(_this._opt.container).append('', '<div class="gallery gallery--hidden ' + _this._opt.elementName + '"></div>');
    }
    _this.galleryElement = $('.' + _this._opt.elementName);
    if (_this._opt.fullScreen) {
      _this.galleryElement.find('.gallery__inner-wrapper').append('', '<div class="gallery__img-holder">' +
        '<img class="gallery__img-preview" src="' + _this._opt.images[_this._opt.start - 1] + '"/>' +
        '</div>');
    } else {
      _this.galleryElement.append('', '<div class="gallery__img-holder">' +
        '<img class="gallery__img-preview" src="' + _this._opt.images[_this._opt.start - 1] + '"/>' +
        '</div>');
    }
    if (_this._opt.thumbnail) {
      _this.galleryElement.append('', renderThumbnail(options.images)).append(controls());
    } else {
      _this.galleryElement.append('', controls());
    }

    if (_this._opt.autoPlay) {
      Gallery.start();
    }
    bindClickEvents(_this.galleryElement);
  };

  Gallery.prototype = {
    start: function() {
      _this.goTo(_this._opt.currIndex);
      _this.galleryElement.removeClass('gallery--hidden');
      console.log('started', _this._opt);
      if (_this._opt.fullScreen) {
        $('.pop-up__model').addClass('is_visible');
      }
    },
    stop: function() {
      _this.galleryElement.addClass('gallery--hidden');
    },
    next: function() {
      if (_this._opt.currIndex <= (_this._opt.imagesLength - 1)) {
        _this._opt.currIndex++;
        _this.goTo(_this._opt.currIndex);
      }
    },
    prev: function() {
      if ((_this._opt.currIndex - 1) !== 0) {
        _this._opt.currIndex--;
        _this.goTo(_this._opt.currIndex);
      }
    },
    goTo: function(imgNo) {
      var thumbnail;
      _this._opt.currIndex = Number(imgNo);
      if (_this._opt.images[imgNo - 1]) {
        _this.galleryElement.find('.gallery__img-preview').attr('src', _this._opt.images[imgNo - 1]);
      }
      if (_this._opt.thumbnail) {
        thumbnail = _this.galleryElement.find('.thumbnail__list');
        thumbnail.removeClass('active');
        thumbnail.eq(imgNo - 1).addClass('active');
      }
      togglePrevNext(_this);
    },
    destroy: function() {
      console.log('destroyed');
    }
  };

  return Gallery;
}

var imgArr = ['images/placeholder-1.png', 'images/placeholder-2.png', 'images/placeholder-3.jpg', 'images/placeholder-4.jpg'];


var hotelPhotosGallery = new Gallery({
  images: imgArr,
  autoPlay: true,
  container: '.photo-list'
});
hotelPhotosGallery.init();
nem035
  • 34,790
  • 6
  • 87
  • 99
Suresh Pattu
  • 6,083
  • 16
  • 59
  • 91

2 Answers2

2

After looking a bit at your code, I found the problem:

if (_this._opt.autoPlay) {
  Gallery.start(); // This method doesn't exist on a non instantiated object
}

You are trying to call a method on the function itself, not a method on the prototype of an instance of that function (instantiated using new).

However, prototype methods won't be available on non-instantiated items because this in a function gets attached to the global object by default. When using new, that this actually becomes a new object and gets implicitly returned from the function, thus creating a new instance. More about using function constructors

Generally, this would be fixed by using the context (i.e. this) instead of the function name:

if (_this._opt.autoPlay) {
  this.start(); // Use the context of the instantiated Gallery
}

In your particular case however, you are re-defining the prototype:

Gallery.prototype = { ... } // You are overwriting the prototype object

Instead of using the one that the JS engine already prepared for you and add methods on it:

// add methods directly on the prototype
Gallery.prototype.method1 = // ...
Gallery.prototype.method2 = // ...

This way is potentially unsafe since it will end up removing anything else that existed in the prototype before you re-defined it.

If you are not concerned with this and would like to keep your code the way it is, you can access your method also by explicitly calling the prototype:

if (_this._opt.autoPlay) {
  Gallery.prototype.start(); // Explicitly call the prototype, since you already defined it
}
Community
  • 1
  • 1
nem035
  • 34,790
  • 6
  • 87
  • 99
1

you are not calling it properly. if you need to call prototype function it must be like

Gallery.prototype.start()

if you are trying to implement a special behavior (object based) then it should be like this. also here are some different ways of simulating the class in JavaScript

Community
  • 1
  • 1
Umer Hayyat
  • 396
  • 1
  • 5
  • 13