I am building a site using the Fotorama (www.fotorama.io) Jquery plugin. I have been using this code to generate custom thumbnails (jsfiddle):
$('.thumbs').each(function () {
$('a', this).each(function () {
var $a = $(this);
// set ids, will use them later
$a.attr({id: $a.attr('href').replace(/[\/\.-]/g, '')});
});
var $thumbs = $(this),
$fotorama = $thumbs.clone();
$fotorama
.on('fotorama:show', function (e, fotorama) {
// pick the active thumb by id
$('#' + fotorama.activeFrame.id)
.addClass('active')
.siblings()
.removeClass('active');
})
.addClass('fotorama')
.removeClass('thumbs')
.insertAfter(this)
.fotorama({nav: false, width: '100%', maxHeight: 400, ratio: 3/2});
// get access to the API
var fotorama = $fotorama.data('fotorama');
$thumbs.on('click', 'a', function (e) {
e.preventDefault();
// show frame by id
fotorama.show(this.id);
});
});
This code works very well but I also need custom captions. I found the following code (jsfiddle) but I am struggling with putting the two bits of code together. I'm just learning Javascript and JQuery and could do with a hand combining the script to work as one. Everything I have tried so far has failed.
$('.fotorama')
.on('fotorama:show', function (e, fotorama) {
fotorama.$caption = fotorama.$caption || $(this).next('.fotorama-caption');
var activeFrame = fotorama.activeFrame;
fotorama.$caption.html(
'<strong>' + activeFrame.title + '</strong><br>'
+ activeFrame.author
);
})
.fotorama();
Solved with the help of isherwood. Please find solution here http://jsfiddle.net/vCUC2/97/
$('.thumbs').each(function () {
$('a', this).each(function () {
var $a = $(this);
// set ids, will use them later
$a.attr({
id: $a.attr('href').replace(/[\/\.-]/g, '')
});
});
var $thumbs = $(this),
$fotorama = $thumbs.clone();
$fotorama.on('fotorama:show', function (e, fotorama) {
// pick the active thumb by id
$('#' + fotorama.activeFrame.id)
.addClass('active')
.siblings()
.removeClass('active');
var activeFrame = fotorama.activeFrame;
fotorama.$caption = fotorama.$caption || $(this).next('.fotorama-caption');
fotorama.$caption.html(
'Title: <strong>' + activeFrame.title + '</strong><br>Caption: ' + activeFrame.author);
})
.addClass('fotorama')
.removeClass('thumbs')
.insertAfter(this)
.fotorama({
nav: false,
width: '100%',
maxHeight: 400,
ratio: 3 / 2
});
// get access to the API
var fotorama = $fotorama.data('fotorama');
$thumbs.on('click', 'a', function (e) {
e.preventDefault();
// show frame by id
fotorama.show(this.id);
});
});
Related
An item I've submitted to themeforest.net got soft rejected with the following message:
PROPER EVENT BINDING: Consider using the preferred .on() method rather than .click(), .bind(), .hover(), etc. For best performance and concise code use event delegation whenever possible
I have no idea what to do actually and would appreciate some help.
This is my code (it’s quite long sorry):
jQuery(document).ready(function($) {
"use strict";
// PRELOADER
$(window).load(function() {
$('#preloader').fadeOut('slow', function() {
$(this).remove();
});
});
// NAV BR RESIZING
$(document).on("scroll", function() {
if ($(document).scrollTop() > 50) {
$("header").removeClass("large").addClass("small");
} else {
$("header").removeClass("small").addClass("large");
}
});
// MOBILE MENU TRIGGER
$('.menu-item').addClass('menu-trigger');
$('.menu-trigger').click(function() {
$('#menu-trigger').toggleClass('clicked');
$('.container').toggleClass('push');
$('.pushmenu').toggleClass('open');
});
// SEARCH
$('.search').click(function(e) {
$(".search-overlay").addClass("visible");
e.preventDefault();
});
$('.close-search').click(function(e) {
$(".search-overlay").removeClass("visible");
e.preventDefault();
});
// FOUNDATION INITIALIZER
$(document).foundation();
// LIGHTCASE
$('a[data-rel^=lightcase]').lightcase({
showSequenceInfo: false,
});
// CONTDOWN
$('[data-countdown]').each(function() {
var $this = $(this),
finalDate = $(this).data('countdown');
$this.countdown(finalDate, function(event) {
$this.html(event.strftime('' +
'<span class="time">%D <span>days</span></span> ' +
'<span class="time">%H <span>hr</span></span> ' +
'<span class="time">%M <span>min</span></span> ' +
'<span class="time">%S <span>sec</span></span>'));
});
});
// SCROLLDOWN BUTTON
$(".show-scrolldown-btn").append("<div class='scrolldown-btn reveal-from-bottom'></div>")
$('.scrolldown-btn').on('click', function() {
var ele = $(this).closest("div");
// this will search within the section
$("html, body").animate({
scrollTop: $(ele).offset().top + 70
}, 500);
return false;
});
// ISOTOPE MASONRY
$(window).load(function() {
var $container = $('.grid');
$container.isotope({
itemSelector: '.grid-item',
columnWidth: '.grid-sizer',
});
var $optionSets = $('.filter'),
$optionLinks = $optionSets.find('a');
$optionLinks.click(function() {
var $this = $(this);
if ($this.hasClass('active')) {
return false;
}
var $optionSet = $this.parents('.filter');
$optionSet.find('.active').removeClass('active');
$this.addClass('active');
// make option object dynamically, i.e. { filter: '.my-filter-class' }
var options = {},
key = $optionSet.attr('data-option-key'),
value = $this.attr('data-option-value');
value = value === 'false' ? false : value;
options[key] = value;
if (key === 'layoutMode' && typeof changeLayoutMode === 'function') {
changeLayoutMode($this, options);
} else {
$container.isotope(options);
}
return false;
});
});
//BACK TO TOP
var offset = 300,
offset_opacity = 1200,
scroll_top_duration = 700,
$back_to_top = $('.backtotop');
$(window).scroll(function() {
($(this).scrollTop() > offset) ? $back_to_top.addClass('is-visible'): $back_to_top.removeClass('is-visible fade-out');
if ($(this).scrollTop() > offset_opacity) {
$back_to_top.addClass('fade-out');
}
});
$back_to_top.on('click', function(event) {
event.preventDefault();
$('body,html').animate({
scrollTop: 0,
}, scroll_top_duration);
});
});
So you would change event listener assignments like the following:
$('.search').click(function(e) {
$(".search-overlay").addClass("visible");
e.preventDefault();
});
...to use the corresponding on method instead, passing the event name as an argument:
$('.search').on("click", function(e) {
$(".search-overlay").addClass("visible");
e.preventDefault();
});
Event delegation is avoiding adding several event listeners to specific nodes and instead adding a single event listener to a common parent element, which then looks to see which child element was clicked on.
There's a good article here:
https://www.google.co.uk/amp/s/davidwalsh.name/event-delegate/amp
I am querying the Twitch API for a list of users in my database, to see if they are online.
I am essentially listing them all, with "display: none" and then unhiding if online:
$('.online_list').each(function (index) {
var tnick = $(this).data('tnick');
$.getJSON("https://api.twitch.tv/kraken/streams?client_id={:twitch_key}&channel="+tnick+"", function(a) {
if (a["streams"].length > 0)
{
alert(tnick);
$(this).show();
console.log(index + ": " + $( this ).text());
}
});
});
In my testing, the alert(tnick) works perfectly, so I know it's running. The problem is $(this).show(); just isn't working.
Here's example HTML:
<div class="online_list" data-tnick="test" style="display: none;">test:Twitch <span>(Online)</span></div>
<div class="online_list" data-tnick="test2" style="display: none;">test2:Twitch <span>(Online)</span></div>
this is the current scope object!
to fix your code you can do the following:
$('.online_list').each(function (index) {
var $that = $(this); // create a temp var that
var tnick = $that.data('tnick');
$.getJSON("https://api.twitch.tv/kraken/streams?client_id={:twitch_key}&channel="+tnick+"", function(a) {
if (a && a["streams"] && a["streams"].length > 0) {
alert(tnick);
$that.show(); // use that instead of this
console.log(index + ": " + $that.text());
}
});
});
Check out this resource for more information on scope & this: http://javascriptplayground.com/blog/2012/04/javascript-variable-scope-this/
EDIT:
Also, like #djxak suggests, you can use the element parameter of the callback, that is more simple and clean in my opinion.
The #djxak suggests:
$('.online_list').each(function (index, element) {
//... (scope1 code)
$.getJSON("", function(a) {
//... (scope2 code)
$(element).show();
});
});
My approach:
$('.online_list').each(function (index) {
//... (scope1 code)
var $current = $(this);
$.getJSON("", function(a) {
//... (scope2 code)
$current.show();
});
});
Info about each function and element parameter in jQuery docs: https://api.jquery.com/each/#each-function
I'm following the Javascript & jQuery book by Jon Duckett. In it, I'm trying to recreate this example project.
The bit that's not working is this: (my code)
Func. to update counter:
var updateCounter = function(){
var itemCount = $('ul li[class!=complete]').length;
$('#counter').text(itemCount);
}
updateCounter();
Func. to check if item is complete:
var comp = function(){
var clickedItem = $(this)
if(clickedItem.hasClass('complete')){
clickedItem.animate({
opacity: 0.0
}, 500, 'swing', function(){
clickedItem.remove();
});
}
else{
$(this).addClass('complete');
var removedItem = $(this).clone();
$(this).remove();
list.append(removedItem);
}
updateCounter();
}
Func. to add new item:
var addNewItem = function(e){
e.preventDefault();
var firstComplete = $('ul>.complete').first();
var newItem = $('#itemDescription:input:text').val();
if (firstComplete.length !==0){
firstComplete.before('<li>' + newItem + '</li>');
hideFormControls();
}
else{
list.append('<li>' + newItem + '</li>');
hideFormControls();
}
updateCounter();
}
The difference between my way, and the book, is that when adding a new item to the list, the book types out
list.append('<li class=\"complete\">' + newItem + '</li>')
whereas, I use addClass. And doing that doesn't update the counter (it does, but it's off by one). I tried adding an event handler to the list:
$('ul').on('DOMSubtreeModified', updateCounter);
It does not update correctly because you are counting the elements before it is removed. The animation is asynchronous.
clickedItem.animate({ //this is asynchronous
opacity: 0.0
}, 500, 'swing', function(){ //this is called after updateCounter() was called
clickedItem.remove();
updateCounter(); //count after it is removed
});
I'm no JS expert but I'm trying to alter this so that if there is a royalslider display it… if there isn't display the static image and the title and description. Any ideas as to why this isn't working? my head is currently spinning… I've left some space around the section I'm trying to add to the code under //royal slider fix and currently its just showing the title and description from the if statement. But, the markup is showing the slider div and outputting the code.
Any help would be very appreciated! You can preview this code and what I'm trying to do here... http://bvh.delineamultimedia.com/?page_id=2
;(function($) {
$.fn.SuperBox = function(options) {
var superbox = $('<div class="superbox-show"></div>');
var superboximg = $('<img src="" class="superbox-current-img">');
var superboxclose = $('<div class="superbox-close"></div>');
superbox.append(superboximg).append(superboxclose);
return this.each(function() {
$('.superbox').on('click', '.superbox-list', function() {
//allows for superbox to work inside of quicksand
$('ul.filterable-grid').css({overflow: 'visible'});
var currentimg = $(this).find('.superbox-img');
superbox.find('.title').remove();
superbox.find('.description').remove();
var imgData = currentimg.data();
superboximg.attr('src', imgData.img);
if (imgData.title) { superbox.append('<h3 class="title">'+imgData.title+'</h3>'); }
if (imgData.description) { superbox.append('<div class="description">' + imgData.description + '</div>'); }
//royal slider fix
superbox.find('.royalSlider').remove(); // remove the slider from previous events
var imgData = currentimg.data();
var sliderData = currentimg.next('.royalSlider'); // grab the slider html that we want to insert
superboximg.attr('src', imgData.img);
if (sliderData) { // show the slider if there is one
superbox.clone().append(sliderData); // clone the element so we don't loose it for the next time the user clicks
} else { // if there is no slider proceed as before
if (imgData.img) {
superbox.append(imgData.img);
}
if (imgData.title) {
superbox.append('<h3 class="title">' + imgData.title + '</h3>');
}
if (imgData.description) {
superbox.append('<div class="description">' + imgData.description + '</div>');
}
}
if($('.superbox-current-img').css('opacity') == 0) {
$('.superbox-current-img').animate({opacity: 1});
}
if ($(this).next().hasClass('superbox-show')) {
superbox.toggle();
} else {
superbox.insertAfter(this).css('display', 'block');
}
$('html, body').animate({
scrollTop:superbox.position().top - currentimg.width()
}, 'medium');
});
$('.superbox').on('hover', '.superbox-list', function(e) {
$(this).find('.overlay').stop()[(e.type == 'mouseenter') ? 'fadeIn' : 'fadeOut']('slow');
});
$('.superbox').on('click', '.superbox-close', function() {
$('.superbox-current-img').animate({opacity: 100}, 200, function() {
$('.superbox-show').slideUp();
});
});
});
};
})(jQuery);
This is only intended to be hints, not an attempt to solve the entire problem.
Try this:
var superbox = $('<div class="superbox-show"/>');
var superboximg = $('<img src="" class="superbox-current-img"/>');
var superboxclose = $('<div class="superbox-close"/>');
if (sliderData.length > 0)
Where is imgData.img getting its value?
In drupal i have generated a list where each item is a fieldset with collapsible, that can contain extra information.
Because of the rather large list i want to avoid loading the extra information until a user clicks on the fieldset.
Best case scenario:
User clicks on collapsed fieldset.
Fieldset loads extra information.
Fieldset uncollapses.
I've copied and loaded the copy of collapse.js into my form, but I'm very new to js and jQuery, so I'm a little lost. If someone can show me how to call a function the first time the fieldset is expanded, I'm sure i can figure out the rest.
I've included the code from collapse.js:
(function ($) {
//Toggle the visibility of a fieldset using smooth animations.
Drupal.toggleFieldset = function (fieldset) {
var $fieldset = $(fieldset);
if ($fieldset.is('.collapsed')) {
var $content = $('> .fieldset-wrapper', fieldset).hide();
$fieldset
.removeClass('collapsed')
.trigger({ type: 'collapsed', value: false })
.find('> legend span.fieldset-legend-prefix').html(Drupal.t('Hide'));
$content.slideDown({
duration: 'fast',
easing: 'linear',
complete: function () {
Drupal.collapseScrollIntoView(fieldset);
fieldset.animating = false;
},
step: function () {
// Scroll the fieldset into view.
Drupal.collapseScrollIntoView(fieldset);
}
});
}
else {
$fieldset.trigger({ type: 'collapsed', value: true });
$('> .fieldset-wrapper', fieldset).slideUp('fast', function () {
$fieldset
.addClass('collapsed')
.find('> legend span.fieldset-legend-prefix').html(Drupal.t('Show'));
fieldset.animating = false;
});
}
};
//Scroll a given fieldset into view as much as possible.
Drupal.collapseScrollIntoView = function (node) {
var h = document.documentElement.clientHeight || document.body.clientHeight || 0;
var offset = document.documentElement.scrollTop || document.body.scrollTop || 0;
var posY = $(node).offset().top;
var fudge = 55;
if (posY + node.offsetHeight + fudge > h + offset) {
if (node.offsetHeight > h) {
window.scrollTo(0, posY);
}
else {
window.scrollTo(0, posY + node.offsetHeight - h + fudge);
}
}
};
Drupal.behaviors.collapse = {
attach: function (context, settings) {
$('fieldset.collapsible', context).once('collapse', function () {
var $fieldset = $(this);
// Expand fieldset if there are errors inside, or if it contains an
// element that is targeted by the uri fragment identifier.
var anchor = location.hash && location.hash != '#' ? ', ' + location.hash : '';
if ($('.error' + anchor, $fieldset).length) {
$fieldset.removeClass('collapsed');
}
var summary = $('<span class="summary"></span>');
$fieldset.
bind('summaryUpdated', function () {
var text = $.trim($fieldset.drupalGetSummary());
summary.html(text ? ' (' + text + ')' : '');
})
.trigger('summaryUpdated');
// Turn the legend into a clickable link, but retain span.fieldset-legend
// for CSS positioning.
var $legend = $('> legend .fieldset-legend', this);
$('<span class="fieldset-legend-prefix element-invisible"></span>')
.append($fieldset.hasClass('collapsed') ? Drupal.t('Show') : Drupal.t('Hide'))
.prependTo($legend)
.after(' ');
// .wrapInner() does not retain bound events.
var $link = $('<a class="fieldset-title" href="#"></a>')
.prepend($legend.contents())
.appendTo($legend)
.click(function () {
var fieldset = $fieldset.get(0);
// Don't animate multiple times.
if (!fieldset.animating) {
fieldset.animating = true;
Drupal.toggleFieldset(fieldset);
}
return false;
});
$legend.append(summary);
});
}
};
})(jQuery);
It looks to me like you'd have to override the whole Drupal.toggleFieldset function (just like when you are overriding a Drupal theme function.
You could perhaps add a class to the fieldset in FormAPI then catch it in the complete function of the $content.slideDown params and fire a custom function of yours, to add a 'loading' graphic and make your ajax request.
I'm assuming from your question that you are familiar enough with FormAPI/jQuery.ajax() to have a go. But let me know if not and i'll include some snippets
EDIT
Here is some example code, it'd take a quite a while to setup a test environment for this, so it'just a pointer (cant create a JS fiddle for this ;))
You might add your fieldset like this in PHP
$form['my_fieldset'] = array(
'#type' = 'fieldset',
'#title' = t('My fieldset'),
'#collapsible' = true,
'#collapsed' = true,
'#attributes' => array(
'class' => array('ajax-fieldset'),
'rel' => 'callback/url/path' // random attribute to store the link to a menu path that will return your HTML
)
);
$form['my_fieldset'] = array(
'#markup' => '<div class="response">loading...</div>'
);
You'll also obviously have setup a menu hook returning your themed data # callback/url/path. IMO it's better to return JSON data and theme them in with JS templating, but the Drupal way (for the moment at least) seems to be to render HTML in the menu hook callback function.
Then here is the JS. I've only included the altered complete function, rather than reproduce what you pasted. Add the complete function in to a copy of the code the re-specify the core Drupal function in your own JS file
$content.slideDown({
complete: function () {
Drupal.collapseScrollIntoView(fieldset);
fieldset.animating = false;
if($fieldset.hasClass('ajax-fieldset')) {
$.get(
Drupal.settings.basePath + $fielset.attr('rel'),
function(data) {
$fieldset.find('.response').html(data);
}
)
}
}
});
Or, rather than messing around with the collapsible function. just create your own fieldset without the collapsible/collapsed classes and implement from scratch yourself
....so.. something like that :)