I am having trouble with my website's navigation bar.
It is supposed to detect and highlight the navigation link when the user has reached the Section ID. However, it seems to be detecting the section well above their target.
How would I get the navigation bar to accurately detect the sections?
Just in case, here's the jQuery smooth-scrolling code:
;(function($, window, document, undefined){
// our plugin constructor
var OnePageNav = function(elem, options){
this.elem = elem;
this.$elem = $(elem);
this.options = options;
this.metadata = this.$elem.data('plugin-options');
this.$win = $(window);
this.sections = {};
this.didScroll = false;
this.$doc = $(document);
this.docHeight = this.$doc.height();
};
// the plugin prototype
OnePageNav.prototype = {
defaults: {
navItems: 'a',
currentClass: 'current',
changeHash: false,
easing: 'swing',
filter: '',
scrollSpeed: 750,
scrollThreshold: 0.5,
begin: false,
end: false,
scrollChange: false
},
init: function() {
// Introduce defaults that can be extended either
// globally or using an object literal.
this.config = $.extend({}, this.defaults, this.options, this.metadata);
this.$nav = this.$elem.find(this.config.navItems);
//Filter any links out of the nav
if(this.config.filter !== '') {
this.$nav = this.$nav.filter(this.config.filter);
}
//Handle clicks on the nav
this.$nav.on('click.onePageNav', $.proxy(this.handleClick, this));
//Get the section positions
this.getPositions();
//Handle scroll changes
this.bindInterval();
//Update the positions on resize too
this.$win.on('resize.onePageNav', $.proxy(this.getPositions, this));
return this;
},
adjustNav: function(self, $parent) {
self.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass);
$parent.addClass(self.config.currentClass);
},
bindInterval: function() {
var self = this;
var docHeight;
self.$win.on('scroll.onePageNav', function() {
self.didScroll = true;
});
self.t = setInterval(function() {
docHeight = self.$doc.height();
//If it was scrolled
if(self.didScroll) {
self.didScroll = false;
self.scrollChange();
}
//If the document height changes
if(docHeight !== self.docHeight) {
self.docHeight = docHeight;
self.getPositions();
}
}, 250);
},
getHash: function($link) {
return $link.attr('href').split('#')[1];
},
getPositions: function() {
var self = this;
var linkHref;
var topPos;
var $target;
self.$nav.each(function() {
linkHref = self.getHash($(this));
$target = $('#' + linkHref);
if($target.length) {
topPos = $target.offset().top;
self.sections[linkHref] = Math.round(topPos);
}
});
},
getSection: function(windowPos) {
var returnValue = null;
var windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold);
for(var section in this.sections) {
if((this.sections[section] - windowHeight) < windowPos) {
returnValue = section;
}
}
return returnValue;
},
handleClick: function(e) {
var self = this;
var $link = $(e.currentTarget);
var $parent = $link.parent();
var newLoc = '#' + self.getHash($link);
if(!$parent.hasClass(self.config.currentClass)) {
//Start callback
if(self.config.begin) {
self.config.begin();
}
//Change the highlighted nav item
self.adjustNav(self, $parent);
//Removing the auto-adjust on scroll
self.unbindInterval();
//Scroll to the correct position
self.scrollTo(newLoc, function() {
//Do we need to change the hash?
if(self.config.changeHash) {
window.location.hash = newLoc;
}
//Add the auto-adjust on scroll back in
self.bindInterval();
//End callback
if(self.config.end) {
self.config.end();
}
});
}
e.preventDefault();
},
scrollChange: function() {
var windowTop = this.$win.scrollTop();
var position = this.getSection(windowTop);
var $parent;
//If the position is set
if(position !== null) {
$parent = this.$elem.find('a[href$="#' + position + '"]').parent();
//If it's not already the current section
if(!$parent.hasClass(this.config.currentClass)) {
//Change the highlighted nav item
this.adjustNav(this, $parent);
//If there is a scrollChange callback
if(this.config.scrollChange) {
this.config.scrollChange($parent);
}
}
}
},
scrollTo: function(target, callback) {
var offset = $(target).offset().top;
$('html, body').animate({
scrollTop: offset
}, this.config.scrollSpeed, this.config.easing, callback);
},
unbindInterval: function() {
clearInterval(this.t);
this.$win.unbind('scroll.onePageNav');
}
};
OnePageNav.defaults = OnePageNav.prototype.defaults;
$.fn.onePageNav = function(options) {
return this.each(function() {
new OnePageNav(this, options).init();
});
};
})( jQuery, window , document );
Related
I have a Multi-Level Accordion Menu - from a FAQ - with 2 levels.
Each level have a 13 Questions / Answers.
My problem:
One of the Questions/Answers have a lot of information. (almost 10 pages).
The other Questions/Answers have 3, 4 lines of information each.
The Question with many information is number 7.
Then When I click on question 7 - the answer 7 is open. with many information, and the user must scroll down the page to read all.
Until here, OK. no problem.
but at end - If I click on Question 8 - to read the answer, the menu closes question 7 (ok) - open question 8 (ok) - but the page still in the footer - the page dont scrolls up with the faq..
With all other questions - because they are small - I can see in same screen almost all 13 questions + the answer opened.
With question 7 - no. the page scrolls down and don't scrolls up - when we close the question 7.
There is a way to solve this? I think I need to create an anchor for each question..
Tks a lot!
https://jsfiddle.net/27sdLvmh/
Please, click on Question 7, scrool down to read the answer and then click on question 8. the menu scrolls up - but the screen no.. and the user was lost in the footer of the page.. :D
JS:
;(function ( $, window, document, undefined ) {
var pluginName = 'accordion',
defaults = {
transitionSpeed: 300,
transitionEasing: 'ease',
controlElement: '[data-control]',
contentElement: '[data-content]',
groupElement: '[data-accordion-group]',
singleOpen: true
};
function Accordion(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Accordion.prototype.init = function () {
var self = this,
opts = self.options;
var $accordion = $(self.element),
$controls = $accordion.find('> ' + opts.controlElement),
$content = $accordion.find('> ' + opts.contentElement);
var accordionParentsQty = $accordion.parents('[data-accordion]').length,
accordionHasParent = accordionParentsQty > 0;
var closedCSS = { 'max-height': 0, 'overflow': 'hidden' };
var CSStransitions = supportsTransitions();
function debounce(func, threshold, execAsap) {
var timeout;
return function debounced() {
var obj = this,
args = arguments;
function delayed() {
if (!execAsap) func.apply(obj, args);
timeout = null;
};
if (timeout) clearTimeout(timeout);
else if (execAsap) func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
}
function supportsTransitions() {
var b = document.body || document.documentElement,
s = b.style,
p = 'transition';
if (typeof s[p] == 'string') {
return true;
}
var v = ['Moz', 'webkit', 'Webkit', 'Khtml', 'O', 'ms'];
p = 'Transition';
for (var i=0; i<v.length; i++) {
if (typeof s[v[i] + p] == 'string') {
return true;
}
}
return false;
}
function requestAnimFrame(cb) {
if(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame) {
return requestAnimationFrame(cb) ||
webkitRequestAnimationFrame(cb) ||
mozRequestAnimationFrame(cb);
} else {
return setTimeout(cb, 1000 / 60);
}
}
function toggleTransition($el, remove) {
if(!remove) {
$content.css({
'-webkit-transition': 'max-height ' + opts.transitionSpeed + 'ms ' + opts.transitionEasing,
'transition': 'max-height ' + opts.transitionSpeed + 'ms ' + opts.transitionEasing
});
} else {
$content.css({
'-webkit-transition': '',
'transition': ''
});
}
}
function calculateHeight($el) {
var height = 0;
$el.children().each(function() {
height = height + $(this).outerHeight(true);
});
$el.data('oHeight', height);
}
function updateParentHeight($parentAccordion, $currentAccordion, qty, operation) {
var $content = $parentAccordion.filter('.open').find('> [data-content]'),
$childs = $content.find('[data-accordion].open > [data-content]'),
$matched;
if(!opts.singleOpen) {
$childs = $childs.not($currentAccordion.siblings('[data-accordion].open').find('> [data-content]'));
}
$matched = $content.add($childs);
if($parentAccordion.hasClass('open')) {
$matched.each(function() {
var currentHeight = $(this).data('oHeight');
switch (operation) {
case '+':
$(this).data('oHeight', currentHeight + qty);
break;
case '-':
$(this).data('oHeight', currentHeight - qty);
break;
default:
throw 'updateParentHeight method needs an operation';
}
$(this).css('max-height', $(this).data('oHeight'));
});
}
}
function refreshHeight($accordion) {
if($accordion.hasClass('open')) {
var $content = $accordion.find('> [data-content]'),
$childs = $content.find('[data-accordion].open > [data-content]'),
$matched = $content.add($childs);
calculateHeight($matched);
$matched.css('max-height', $matched.data('oHeight'));
}
}
function closeAccordion($accordion, $content) {
$accordion.trigger('accordion.close');
if(CSStransitions) {
if(accordionHasParent) {
var $parentAccordions = $accordion.parents('[data-accordion]');
updateParentHeight($parentAccordions, $accordion, $content.data('oHeight'), '-');
}
$content.css(closedCSS);
$accordion.removeClass('open');
} else {
$content.css('max-height', $content.data('oHeight'));
$content.animate(closedCSS, opts.transitionSpeed);
$accordion.removeClass('open');
}
}
function openAccordion($accordion, $content) {
$accordion.trigger('accordion.open');
if(CSStransitions) {
toggleTransition($content);
if(accordionHasParent) {
var $parentAccordions = $accordion.parents('[data-accordion]');
updateParentHeight($parentAccordions, $accordion, $content.data('oHeight'), '+');
}
requestAnimFrame(function() {
$content.css('max-height', $content.data('oHeight'));
});
$accordion.addClass('open');
} else {
$content.animate({
'max-height': $content.data('oHeight')
}, opts.transitionSpeed, function() {
$content.css({'max-height': 'none'});
});
$accordion.addClass('open');
}
}
function closeSiblingAccordions($accordion) {
var $accordionGroup = $accordion.closest(opts.groupElement);
var $siblings = $accordion.siblings('[data-accordion]').filter('.open'),
$siblingsChildren = $siblings.find('[data-accordion]').filter('.open');
var $otherAccordions = $siblings.add($siblingsChildren);
$otherAccordions.each(function() {
var $accordion = $(this),
$content = $accordion.find(opts.contentElement);
closeAccordion($accordion, $content);
});
$otherAccordions.removeClass('open');
}
function toggleAccordion() {
var isAccordionGroup = (opts.singleOpen) ? $accordion.parents(opts.groupElement).length > 0 : false;
calculateHeight($content);
if(isAccordionGroup) {
closeSiblingAccordions($accordion);
}
if($accordion.hasClass('open')) {
closeAccordion($accordion, $content);
} else {
openAccordion($accordion, $content);
}
}
function addEventListeners() {
$controls.on('click', toggleAccordion);
$controls.on('accordion.toggle', function() {
if(opts.singleOpen && $controls.length > 1) {
return false;
}
toggleAccordion();
});
$(window).on('resize', debounce(function() {
refreshHeight($accordion);
}));
}
function setup() {
$content.each(function() {
var $curr = $(this);
if($curr.css('max-height') != 0) {
if(!$curr.closest('[data-accordion]').hasClass('open')) {
$curr.css({ 'max-height': 0, 'overflow': 'hidden' });
} else {
toggleTransition($curr);
calculateHeight($curr);
$curr.css('max-height', $curr.data('oHeight'));
}
}
});
if(!$accordion.attr('data-accordion')) {
$accordion.attr('data-accordion', '');
$accordion.find(opts.controlElement).attr('data-control', '');
$accordion.find(opts.contentElement).attr('data-content', '');
}
}
setup();
addEventListeners();
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName,
new Accordion( this, options ));
}
});
}
})( jQuery, window, document );
Hey check this demo
changes line 191 -> 194
setTimeout(function()
{
$('html, body').animate({ scrollTop: $content.prev().offset().top }, "fast");
},(1000/60)+200);
I have a open function that once triggered, simply plays video in a dedicated panel.
This function can be triggered in two ways - one with a click and another one with a page load (window load) with url that contains a valid anchor tag.
They all work fine but some codes of the window load handler are repetitive and I'm not too sure how I can keep this DRY.
Please take a look and point me in some directions on how I can write this better.
I commented in open function which is for which.
$.videoWatch.prototype = {
init: function() {
this.$openLinks = this.$element.find(".open");
this.$closeLinks = this.$element.find(".close");
this.open();
this.close();
},
_getContent: function(element) {
var $parent = element.parent(),
id = element.attr('href').substring(1),
title = $parent.data('title'),
desc = $parent.data('desc');
return {
title: title,
desc: desc,
id: id
}
},
open: function() {
var self = this;
//open theatre with window load with #hash id
window.onload = function() {
var hash = location.hash;
var $a = $('a[href="' + hash + '"]'),
content = self._getContent($a),
$li = $a.parents("li"),
$theatreVideo = $(".playing"),
$theatreTitle = $(".theatre-title"),
$theatreText = $(".theatre-text");
$(".theatre").attr('id', content.id);
$theatreTitle.text(content.title);
$theatreText.text(content.desc);
if ($theatreText.text().length >= 90) {
$(".theatre-text").css({
'overflow': 'hidden',
'max-height': '90px',
});
$moreButton.insertAfter($theatreText);
}
$a.parent().addClass("active");
$(".theatre").insertAfter($li);
$(".theatre").slideDown('fast', scrollToTheatre);
oldIndex = $li.index();
}
//open theatre with click event
self.$openLinks.on("click", function(e) {
// e.preventDefault();
if (curID == $(this).parent().attr("id")) {
$("figure").removeClass("active");
$("button.more").remove();
$(".theatre").slideUp('fast');
$('.playing').attr("src", "");
removeHash();
oldIndex = -1;
curID = "";
return false
} else {
curID = $(this).parent().attr("id");
}
var $a = $(this),
content = self._getContent($a),
$li = $a.parents("li"),
$theatreVideo = $(".playing"),
$theatreTitle = $(".theatre-title"),
$theatreText = $(".theatre-text");
$(".theatre").attr('id', content.id);
$theatreTitle.text(content.title);
$theatreText.text(content.desc);
if ($theatreText.text().length >= 90) {
$(".theatre-text").css({
'overflow': 'hidden',
'max-height': '90px',
});
$moreButton.insertAfter($theatreText);
}
if (!($li.index() == oldIndex)) {
$("figure").removeClass("active");
$(".theatre").hide(function(){
$a.parent().addClass("active");
$(".theatre").insertAfter($li);
$(".theatre").slideDown('fast', scrollToTheatre);
oldIndex = $li.index();
});
} else {
$(".theatre").insertAfter($li);
scrollToTheatre();
$("figure").removeClass("active");
$a.parent().addClass("active");
}
});
},
...
Simplified and refactored open method:
open: function() {
var self = this;
var serviceObj = {
theatreVideo : $(".playing"),
theatre: $(".theatre"),
theatreTitle : $(".theatre-title"),
theatreText : $(".theatre-text"),
setTheatreContent: function(content){
this.theatre.attr('id', content.id);
this.theatreTitle.text(content.title);
this.theatreText.text(content.desc);
if (this.theatreText.text().length >= 90) {
this.theatreText.css({
'overflow': 'hidden',
'max-height': '90px',
});
$moreButton.insertAfter(this.theatreText);
}
},
activateTeatre: function(a, li){
a.parent().addClass("active");
this.theatre.insertAfter(li);
this.theatre.slideDown('fast', scrollToTheatre);
oldIndex = li.index();
}
};
//open theatre with window load with #hash id
window.onload = function() {
var hash = location.hash;
var $a = $('a[href="' + hash + '"]'),
content = self._getContent($a),
$li = $a.parents("li");
serviceObj.setTheatreContent(content);
serviceObj.activateTeatre($a, $li);
}
//open theatre with click event
self.$openLinks.on("click", function(e) {
// e.preventDefault();
if (curID == $(this).parent().attr("id")) {
$("figure").removeClass("active");
$("button.more").remove();
$(".theatre").slideUp('fast');
$('.playing').attr("src", "");
removeHash();
oldIndex = -1;
curID = "";
return false
} else {
curID = $(this).parent().attr("id");
}
var $a = $(this),
content = self._getContent($a),
$li = $a.parents("li");
serviceObj.setTheatreContent(content);
if (!($li.index() == oldIndex)) {
$("figure").removeClass("active");
$(".theatre").hide(function(){
serviceObj.activateTeatre($a, $li);
});
} else {
$(".theatre").insertAfter($li);
scrollToTheatre();
$("figure").removeClass("active");
$a.parent().addClass("active");
}
});
},
1st of all there are variables that don't depend on the input, you could pull them to the class (I'll show just one example, as you asked for directions):
init: function() {
this.$theatreVideo = $(".playing");
All the variables that do depend on the input, like $li could be moved to a function:
var $a = $(this),
$dependsOnA = self.dependsOnA($a);
self.actionDependsOnA($dependsOnA); // see below
function dependsOnA($a) {
return {
a: $a,
li: $a.parents("li"),
content: self._getContent($a)
}
}
Also the code that "repeats" can be moved to a function:
function actionDependsOnA($dependsOnA)
$(".theatre").attr('id', $dependsOnA.content.id);
$theatreTitle.text($dependsOnA.content.title);
$theatreText.text($dependsOnA.content.desc);
}
on my website I have a div .toggle-search that if you click on it it expands to .search-expand where a search form is. This is the code in jQuery
/* Toggle header search
/* ------------------------------------ */
$('.toggle-search').click(function(){
$('.toggle-search').toggleClass('active');
$('.search-expand').fadeToggle(250);
setTimeout(function(){
$('.search-expand input').focus();
}, 300);
});
Now the only way to close the .search-expand is to click once again on the .toggle-search. But I want to change that it closes if you click anywhere else on the site. For an easier example I have the Hueman theme, and I'm talking about the top right corner search option. http://demo.alxmedia.se/hueman/
Thanks!
Add the event on all elements except the search area.
$('body *:not(".search-expand")').click(function(){
$('.toggle-search').removeClass('active');
$('.search-expand').fadeOut(250);
});
or another way,
$('body').click(function(e){
if(e.target.className.indexOf('search-expand') < 0){
$('.toggle-search').removeClass('active');
$('.search-expand').fadeOut(250);
}
});
var isSearchFieldOpen = false;
var $toggleSearch = $('.toggle-search');
var $searchExpand = $('.search-expand');
function toggleSearch() {
// Reverse state
isSearchFieldOpen = !isSearchFieldOpen;
$toggleSearch.toggleClass('active');
// You can use callback function instead of using setTimeout
$searchExpand.fadeToggle(250, function() {
if (isSearchFieldOpen) {
$searchExpand.find('input').focus();
}
});
}
$toggleSearch.on('click', function(e) {
e.stopPropagation();
toggleSearch();
});
$(document.body).on('click', function(e) {
if (isSearchFieldOpen) {
var target = e.target;
// Checking if user clicks outside .search-expand
if (!$searchExpand.is(target) && !$searchExpand.has(target).length) {
toggleSearch();
}
}
});
I have a second search on the site with the same code as before only
with div .toggle-serach2 and .expand-search2, how can i make your code
so it wont overlap. just changing the name to $('toggle-search2')
doesn't cut it
in that case, I would suggest you convert your code into a plugin:
(function($, document) {
var bodyHandlerAttached = false;
var openedForms = [];
var instances = {};
var defaults = {
activeClass: 'active'
};
function ToggleSearch(elem, options) {
this.options = $.extend({}, defaults, options);
this.$elem = $(elem);
this.$btn = $(options.toggleBtn);
this.isOpen = false;
this.id = generateId();
this.bindEvents();
instances[this.id] = this;
if (!bodyHandlerAttached) {
handleOutsideClick();
bodyHandlerAttached = true;
}
}
ToggleSearch.prototype = {
bindEvents: function() {
this.$btn.on('click', $.proxy(toggleHandler, this));
},
open: function() {
if (this.isOpen) { return; }
var _this = this;
this.$btn.addClass(this.options.activeClass);
this.$elem.fadeIn(250, function() {
_this.$elem.find('input').focus();
});
openedForms.push(this.id);
this.isOpen = true;
},
close: function(instantly) {
if (!this.isOpen) { return; }
this.$btn.removeClass(this.options.activeClass);
if (instantly) {
this.$elem.hide();
} else {
this.$elem.fadeOut(250);
}
openedForms.splice(openedForms.indexOf(this.id), 1);
this.isOpen = false;
},
toggle: function() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
};
var toggleHandler = function(ev) {
ev.stopPropagation();
this.toggle();
};
var handleOutsideClick = function(e) {
$(document.body).on('click', function(e) {
if (openedForms.length) {
var target = e.target;
var instance;
for (var id in instances) {
instance = instances[id];
if (!instance.$elem.is(target) && !instance.$elem.has(target).length) {
instance.close(true);
}
}
}
});
};
function generateId() {
return Math.random().toString(36).substr(2, 8);
}
$.fn.toggleSearch = function(options) {
return this.each(function() {
if (!$.data(this, 'toggleSearch')) {
$.data(this, 'toggleSearch', new ToggleSearch(this, options));
}
});
};
})(window.jQuery, document);
And then use it like this:
$('.search-expand').toggleSearch({
toggleBtn: '.toggle-search'
});
$('.search-expand2').toggleSearch({
toggleBtn: '.toggle-search2'
});
JSFiddle example
You could add a click handler to the main window that removes the active class:
$(window).click(function(){
$('.toggle-search').removeClass('active');
}
and then prevent the class removal when you click inside of your toggle-search elem
$('.toggle-search').click(function(e){
e.stopPropagation();
// remainder of click code here
)};
Try to add body click listener
$('body').click(function(e){
if ($(e.target).is('.toggle-search')) return;
$('.toggle-search').removeClass('active');
$('.search-expand').fadeOut(250);
});
For some reason my gallery isn't working on Mobile devices including iPad, works fine on desktop. Instead of allowing a user to click through, all images appear stacked. The link to my site. The code is
located here
// scroll gallery init
function initCarousel() {
var isTouchDevice = /MSIE 10.*Touch/.test(navigator.userAgent) || ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
jQuery('div.view-gallery').scrollGallery({
mask: 'div.frame',
slider: '>ul',
slides: '>li',
btnPrev: 'a.btn-prev',
btnNext: 'a.btn-next',
pagerLinks: '.pagination li',
circularRotation: false,
autoRotation: false,
switchTime: 3000,
animSpeed: 500,
onInit: function(obj){
obj.resizeFlag = true;
obj.win = jQuery(window);
//obj.win.unbind('resize orientationchange load', obj.onWindowResize);
obj.resizeSlides = function(){
obj.slideOffset = obj.slides.eq(0).outerWidth(true) - obj.slides.eq(0).width();
if(!obj.resizeFlag) obj.slides.css({width: ''});
else obj.slides.css({width: obj.mask.width()/2 - obj.slideOffset});
obj.calculateOffsets();
obj.refreshPosition();
obj.refreshState();
}
if(isTouchDevice){
ResponsiveHelper.addRange({
'..767': {
on: function(){
setTimeout(function(){
obj.resizeFlag = true;
obj.resizeSlides();
obj.win.bind('resize orientationchange load', obj.resizeSlides);
}, 100);
}
},
'768..': {
on: function(){
obj.resizeFlag = false;
obj.win.unbind('resize orientationchange load', obj.resizeSlides);
obj.resizeSlides();
}
}
});
}
}
});
jQuery('.scrollable-gallery').scrollableGallery();
}
/*
* scrollableGallery
*/
;(function($) {
function ScrollableGallery(options) {
this.options = {
scrollableArea: '.frame',
listItems: '.list-items',
btnPrev: '.btn-prev',
btnNext: '.btn-next',
animSpeed: 500
}
$.extend(this.options, options);
this.init();
}
ScrollableGallery.prototype = {
init: function() {
this.findElements()
this.setStructure();
this.addEvents();
},
findElements: function() {
this.holder = $(this.options.holder);
this.scrollableArea = this.holder.find(this.options.scrollableArea);
this.listItems = this.scrollableArea.find(this.options.listItems);
this.items = this.listItems.children();
this.lastItem = this.items.last();
this.btnPrev = this.holder.find(this.options.btnPrev);
this.btnNext = this.holder.find(this.options.btnNext);
this.scrollAPI = new jcf.modules.customscroll({
replaces: this.scrollableArea[0]
});
},
setStructure: function() {
var that = this;
if (that.listItems.css('position') === 'static') {
that.listItems.css('position', 'relative');
}
setTimeout(function() {
that.refreshState();
}, 50);
},
refreshState: function() {
this.listItems.css('width', 32700);
this.listItems.css('width', this.lastItem.position().left + this.lastItem.outerWidth(true) + 1);
this.scrollableArea.add(this.scrollableArea.parent()).css({
width: '',
height: ''
});
this.scrollAPI.refreshState();
},
addEvents: function() {
var that = this;
that.btnPrev.bind('click', function(e) {
e.preventDefault();
that.prevSlide();
});
that.btnNext.bind('click', function(e) {
e.preventDefault();
that.nextSlide();
});
win.bind('resize orientationchange load', function() {
that.refreshState();
});
},
nextSlide: function() {
var that = this;
var curPos = this.scrollableArea.scrollLeft();
var pos;
for (var i = 0; i < that.items.length; i++) {
pos = that.items.eq(i).position().left;
if (pos > curPos) {
that.scrollAnimate(curPos, pos);
break;
}
}
},
prevSlide: function() {
var that = this;
var curPos = this.scrollableArea.scrollLeft();
var pos;
for (var i = that.items.length - 1; i >= 0; i--) {
pos = that.items.eq(i).position().left;
if (pos < curPos) {
that.scrollAnimate(curPos, pos);
break;
}
}
},
scrollAnimate: function(from, to) {
var that = this;
var start = new Date().getTime();
setTimeout(function() {
var now = (new Date().getTime()) - start;
var progress = now / that.options.animSpeed;
var result = (to - from) * progress + from;
that.scrollAPI.hScrollBar.scrollTo(result);
if (progress < 1) {
setTimeout(arguments.callee, 10);
} else {
that.scrollAPI.hScrollBar.scrollTo(to);
}
}, 10);
}
}
var win = $(window);
$.fn.scrollableGallery = function(options) {
return this.each(function() {
if (!$(this).data('ScrollableGallery')) {
$(this).data('ScrollableGallery', new ScrollableGallery($.extend({}, {holder: this}, options)));
}
});
}
}(jQuery));
After looking through your code, there were numerous errors with syntax. I have cleaned them up as best as I could, this should help you out.
http://jsfiddle.net/wvWrY/1/
For example, this area was missing a semicolon (no way to call the findElements function, as JS will simply skip to the next line without a semicolon there.)
init: function() {
this.findElements()
this.setStructure();
this.addEvents();
Run your code through a linter, it will greatly improve your syntax structure and ensure little leave out errors like semicolons and commas and brackets aren't omitted.
EDIT: Ok, having looked at your code it appears this is actually due to the !importants in your allmobile.css file. The width and height are set to max-width: 100% (this breaks it because the way the slider works is to extend the gallery as far off screen as possible) and the height to auto (this breaks it because it allows the images to just keep piling on). Once you remove those for the page, it become much much much better and actually works.
I have a list of div's all with a set and equal height/width that are float:left so they sit next to each other and fold under if that parent is smaller than the combined with of the items.
Pretty standard.
This is to create a list of the twitter bootstrap icons, it gives something like this:
I have added next/previous keyboard navigation using the code below, however you will notice that the up/down arrow keys are mapped to call the left/right functions. What I have no idea how to do is to actually do the up/down navigation?
JsFiddle
(function ($) {
$.widget("ui.iconSelect", {
// default options
options: {
},
$select: null,
$wrapper: null,
$list: null,
$filter: null,
$active: null,
icons: {},
keys: {
left: 37,
up: 38,
right: 39,
down: 40
},
//initialization function
_create: function () {
var that = this;
that.$select = that.element;
that.$wrapper = $('<div class="select-icon" tabindex="0"></div>');
that.$filter = $('<input class="span12" tabindex="-1" placeholder="Filter by class name..."/>').appendTo(that.$wrapper);
that.$list = $('<div class="select-icon-list"></div>').appendTo(that.$wrapper);
//build the list of icons
that.element.find('option').each(function () {
var $option = $(this);
var icon = $option.val();
that.icons[icon] = $('<a data-class="' + icon + '"><i class="icon ' + icon + '"></i></a>');
if ($option.is(':selected')) {
that.icons[icon].addClass('selected active');
}
that.$list.append(that.icons[icon]);
});
that.$wrapper.insertBefore(that.$select);
that.$select.addClass('hide');
that._setupArrowKeysHandler();
that._setupClickHandler();
that._setupFilter();
that.focus('selected');
},
focus: function (type) {
var that = this;
if (that.$active === null || that.$active.length == 0) {
if (type == 'first') {
that.$active = that.$list.find('a:visible:first');
} else if (type == 'last') {
that.$active = that.$list.find('a:visible:last');
} else if (type == 'selected') {
that.$active = that.$list.find('a.selected:visible:first');
that.focus('first');
}
}
that.$active.addClass('active');
var toScroll = ((that.$list.scrollTop() + that.$active.position().top)-that.$list.height()/2)+that.$active.height()/2;
//that.$list.scrollTop((that.$list.scrollTop() + top)-that.$list.height()/2);
that.$list.stop(true).animate({
scrollTop: toScroll,
queue: false,
easing: 'linear'
}, 200);
if (type === 'selected') {
return false;
}
that.$select.val(that.$active.data('class'));
that.$select.trigger('change');
},
_setupArrowKeysHandler: function () {
var that = this;
that.$wrapper.on('keydown', function (e) {
switch (e.which) {
case that.keys.left:
that.moveLeft();
break;
case that.keys.up:
that.moveUp();
break;
case that.keys.right:
that.moveRight();
break;
case that.keys.down:
that.moveDown();
break;
case 16:
return true;
case 9:
return true;
break;
default:
that.$filter.focus();
return true;
}
return false;
});
},
_setupFilter: function(){
var that = this;
that.$filter.on('keydown keyup keypress paste cut change', function(e){
that.filter(that.$filter.val());
});
},
_setupClickHandler: function () {
var that = this;
that.$list.on('click', 'a', function () {
that.$wrapper.focus();
that.$active.removeClass('active');
that.$active = $(this);
that.focus('first');
});
},
moveUp: function () {
var that = this;
return that.moveLeft();
},
moveDown: function () {
var that = this;
return that.moveRight();
},
moveLeft: function () {
var that = this;
that.$active.removeClass('active');
that.$active = that.$active.prevAll(':visible:first');
that.focus('last');
return false;
},
moveRight: function () {
var that = this;
that.$active.removeClass('active');
that.$active = that.$active.nextAll(':visible:first');
that.focus('first');
return false;
},
filter: function(word){
var that = this;
var regexp = new RegExp(word.toLowerCase());
var found = false;
$.each(that.icons, function(i, $v){
found = regexp.test(i);
if(found && !$v.is(':visible')){
$v.show();
} else if(!found && $v.is(':visible')){
$v.hide();
}
});
}
});
})(jQuery);
Perhaps something like this: http://jsfiddle.net/QFzCY/
var blocksPerRow = 4;
$("body").on("keydown", function(e){
var thisIndex = $(".selected").index();
var newIndex = null;
if(e.keyCode === 38) {
// up
newIndex = thisIndex - blocksPerRow;
}
else if(e.keyCode === 40) {
// down
newIndex = thisIndex + blocksPerRow;
}
if(newIndex !== null) {
$(".test").eq(newIndex).addClass("selected").siblings().removeClass("selected");
}
});
Basically, you set how many items there are in a row and then find the current index and subtract or add that amount to select the next element via the new index.
If you need to know how many blocks per row there are, you could do this:
var offset = null;
var blocksPerRow = 0;
$(".test").each(function(){
if(offset === null) {
offset = $(this).offset().top;
}
else if($(this).offset().top !== offset) {
return false;
}
blocksPerRow++;
});
To deal with your 'edge' cases, you could do:
if(newIndex >= $(".test").length) {
newIndex = $(".test").length - newIndex;
}
moveUp: function () {
var that = this;
var index = $(this).index();
var containerWidth = parseInt( $('.select-icon-list').innerWidth(), 10);
var iconWidth = parseInt( $('.select-icon-list > a').width(), 10);
var noOfCols = Math.floor( containerWidth / iconWidth );
var newIndex = ( (index - noOfCols) < 0 ) ? index : (index - noOfCols);
var elem = $('.select-icon-list > a')[index];
},
Cache what ever remains static.