(function($) {
$.tools = $.tools || {version: '@VERSION'};
$.tools.scrollable = {
conf: {
size: 5,
vertical: false,
speed: 400,
keyboard: true,
keyboardSteps: null,
disabledClass: 'disabled',
hoverClass: null,
clickable: true,
activeClass: 'active',
easing: 'swing',
loop: false,
items: '.items',
item: null,
prev: '.prev',
next: '.next',
prevPage: '.prevPage',
nextPage: '.nextPage',
api: false,
mousewheel: false,
wheelSpeed: 0,
lazyload: false
}
};
var current;
function Scrollable(root, conf) {
var self = this,
trigger = root.add(self),
horizontal = !conf.vertical,
wrap = root.children(),
index = 0,
forward;
if (!current) { current = self; }
if (wrap.length > 1) { wrap = $(conf.items, root); }
function find(query) {
var els = $(query);
return conf.globalNav ? els : root.parent().find(query);
}
root.data("finder", find);
var prev = find(conf.prev),
next = find(conf.next),
prevPage = find(conf.prevPage),
nextPage = find(conf.nextPage);
$.extend(self, {
getIndex: function() {
return index;
},
getClickIndex: function() {
var items = self.getItems();
return items.index(items.filter("." + conf.activeClass));
},
getConf: function() {
return conf;
},
getSize: function() {
return self.getItems().size();
},
getPageAmount: function() {
return Math.ceil(this.getSize() / conf.size);
},
getPageIndex: function() {
return Math.ceil(index / conf.size);
},
getNaviButtons: function() {
return prev.add(next).add(prevPage).add(nextPage);
},
getRoot: function() {
return root;
},
getItemWrap: function() {
return wrap;
},
getItems: function() {
return wrap.children(conf.item);
},
getVisibleItems: function() {
return self.getItems().slice(index, index + conf.size);
},
seekTo: function(i, time, fn) {
if (i < 0) { i = 0; }
if (index === i) { return self; }
if ($.isFunction(time)) {
fn = time;
}
if (i > self.getSize() - conf.size) {
return conf.loop ? self.begin() : this.end();
}
var item = self.getItems().eq(i);
if (!item.length) { return self; }
var e = $.Event("onBeforeSeek");
trigger.trigger(e, [i]);
if (e.isDefaultPrevented()) { return self; }
if (time === undefined || $.isFunction(time)) { time = conf.speed; }
function callback() {
if (fn) { fn.call(self, i); }
trigger.trigger("onSeek", [i]);
}
if (horizontal) {
wrap.animate({left: -item.position().left}, time, conf.easing, callback);
} else {
wrap.animate({top: -item.position().top}, time, conf.easing, callback);
}
current = self;
index = i;
e = $.Event("onStart");
trigger.trigger(e, [i]);
if (e.isDefaultPrevented()) { return self; }
prev.add(prevPage).toggleClass(conf.disabledClass, i === 0);
next.add(nextPage).toggleClass(conf.disabledClass, i >= self.getSize() - conf.size);
return self;
},
move: function(offset, time, fn) {
forward = offset > 0;
return this.seekTo(index + offset, time, fn);
},
next: function(time, fn) {
return this.move(1, time, fn);
},
prev: function(time, fn) {
return this.move(-1, time, fn);
},
movePage: function(offset, time, fn) {
forward = offset > 0;
var steps = conf.size * offset;
var i = index % conf.size;
if (i > 0) {
steps += (offset > 0 ? -i : conf.size - i);
}
return this.move(steps, time, fn);
},
prevPage: function(time, fn) {
return this.movePage(-1, time, fn);
},
nextPage: function(time, fn) {
return this.movePage(1, time, fn);
},
setPage: function(page, time, fn) {
return this.seekTo(page * conf.size, time, fn);
},
begin: function(time, fn) {
forward = false;
return this.seekTo(0, time, fn);
},
end: function(time, fn) {
forward = true;
var to = this.getSize() - conf.size;
return to > 0 ? this.seekTo(to, time, fn) : self;
},
reload: function() {
trigger.trigger("onReload");
return self;
},
focus: function() {
current = self;
return self;
},
click: function(i) {
var item = self.getItems().eq(i),
klass = conf.activeClass,
size = conf.size;
if (i < 0 || i >= self.getSize()) { return self; }
if (size == 1) {
if (conf.loop) { return self.next(); }
if (i === 0 || i == self.getSize() -1) {
forward = (forward === undefined) ? true : !forward;
}
return forward === false ? self.prev() : self.next();
}
if (size == 2) {
if (i == index) { i--; }
self.getItems().removeClass(klass);
item.addClass(klass);
return self.seekTo(i);
}
if (!item.hasClass(klass)) {
self.getItems().removeClass(klass);
item.addClass(klass);
var delta = Math.floor(size / 2);
var to = i - delta;
if (to > self.getSize() - size) {
to = self.getSize() - size;
}
if (to !== i) {
return self.seekTo(to);
}
}
return self;
}
});
$.each("onBeforeSeek,onStart,onSeek,onReload".split(","), function(i, name) {
if ($.isFunction(conf[name])) {
$(self).bind(name, conf[name]);
}
self[name] = function(fn) {
$(self).bind(name, fn);
return self;
};
});
prev.addClass(conf.disabledClass).click(function() {
self.prev();
});
next.click(function() {
self.next();
});
nextPage.click(function() {
self.nextPage();
});
if (self.getSize() < conf.size) {
next.add(nextPage).addClass(conf.disabledClass);
}
prevPage.addClass(conf.disabledClass).click(function() {
self.prevPage();
});
var hc = conf.hoverClass, keyId = "keydown." + Math.random().toString().substring(10);
self.onReload(function() {
if (hc) {
self.getItems().hover(function() {
$(this).addClass(hc);
}, function() {
$(this).removeClass(hc);
});
}
if (conf.clickable) {
self.getItems().each(function(i) {
$(this).unbind("click.scrollable").bind("click.scrollable", function(e) {
if ($(e.target).is("a")) { return; }
return self.click(i);
});
});
}
if (conf.keyboard) {
$(document).unbind(keyId).bind(keyId, function(evt) {
if (evt.altKey || evt.ctrlKey) { return; }
if (conf.keyboard != 'static' && current != self) { return; }
var s = conf.keyboardSteps;
if (horizontal && (evt.keyCode == 37 || evt.keyCode == 39)) {
self.move(evt.keyCode == 37 ? -s : s);
return evt.preventDefault();
}
if (!horizontal && (evt.keyCode == 38 || evt.keyCode == 40)) {
self.move(evt.keyCode == 38 ? -s : s);
return evt.preventDefault();
}
return true;
});
} else {
$(document).unbind(keyId);
}
if (conf.mousewheel && $.fn.mousewheel) {
root.mousewheel(function(e, delta) {
self.move(delta < 0 ? 1 : -1, conf.wheelSpeed || 50);
return false;
});
}
});
var lconf = $.tools.lazyload && conf.lazyload,
loader,
doLoad = function(ev, i) {
var els = self.getItems().slice(i, i + conf.size);
els.each(function() {
els = els.add($(this).find(":unloaded"));
});
loader.load(els);
};
if (lconf) {
if (typeof lconf != 'object') { lconf = { select: lconf }; }
if (typeof lconf.select != 'string') { lconf.select = "img, :backgroundImage"; }
$.extend(lconf, { growParent: root, api: true }, lconf);
loader = root.find(lconf.select).lazyload(lconf);
self.onBeforeSeek(doLoad);
doLoad(null, 0);
}
self.reload();
}
$.fn.scrollable = function(conf) {
var el = this.data("scrollable");
if (el) { return el; }
conf = $.extend({}, $.tools.scrollable.conf, conf);
conf.keyboardSteps = conf.keyboardSteps || conf.size;
this.each(function() {
el = new Scrollable($(this), conf);
$(this).data("scrollable", el);
});
return conf.api ? el: this;
};
})(jQuery);
