Creating a HTML/JS "plugin" in ES6 - how to refresh DOM nodes - javascript

I am currently in the process of learning ES6. I'm trying to create a carousel which I would usually have written as a JQuery plugin but now instead writing it as an ES6 module so that it can be added to a page's JS using the import keyword.
As the carousel has slides which are absolutely positioned on top of each other, a calculation is done within the JS to determine the tallest carousel slide height and then apply this height to the carousel's UL element.
The module grabs several elements from the DOM within the constructor such as the containing DIV of all carousel elements, the UL of the carousel slides, etc.
class Carousel {
// set up instance variables
constructor (options) {
this.element = options.element;
this.carousel = options.element.querySelectorAll('ul');
this.carouselSlides = this.carousel[0].children;
this.carouselHeight = 0;
}
resize () {
console.log(this.carouselSlides);
//Get tallest slide
Array.prototype.map.call( this.carouselSlides, ( slide ) => {
this.carouselHeight = (slide.offsetHeight > this.carouselHeight) ? slide.offsetHeight : this.carouselHeight;
});
//Set the height of the carousel to the height of its tallest slide
this.carousel[0].style.height = this.carouselHeight+'px';
}
// initial set up
setup () {
this.resize();
window.onresize = this.resize;
}
}
module.exports = Carousel;
As this height will need to be adjusted as the browser width gets smaller I have tried to call the function which does this calculation on window.onresize.
However this does not work. I believe it is because the dom nodes that were assigned to variables in the constructor are cached at their current widths and heights and so the resize function does not use their new values in its calculation.
How can I adjust my code to prevent this caching issue?
Below is a simplified Codepen of my code so far. (I had to add the Carousel module code in with the main script just for Codepen):
http://codepen.io/decodedcreative/pen/vXzGpE/
Thanks

You're problem is related to the context of this. When you assign the callback to the window.resize event, the this is changed to the window:
window.onresize = this.resize;
When the callback is called, this.carouselSlides is undefined because the window doesn't have this property (look at the console to see the errors).
To prevent this problem, bind the callback to the original this (the class instance):
window.onresize = this.resize.bind(this);
You can see it in this codepen.

It turns out there were a couple of issues with my code. Thanks to Ori Drori's help I got to the bottom of them. Here is the fixed code:
class Carousel {
// set up instance variables
constructor (options) {
this.element = options.element;
this.carousel = options.element.querySelectorAll('ul');
this.carouselSlides = this.carousel[0].children;
this.carouselHeight = 0;
}
resize () {
//Get tallest slide
Array.prototype.map.call( this.carouselSlides, ( slide ) => {
this.carouselHeight = (slide.offsetHeight > this.carouselHeight) ? slide.offsetHeight : this.carouselHeight;
});
//Set the height of the carousel to the height of its tallest slide
this.carousel[0].style.height = this.carouselHeight+'px';
//Reset the height of the carousel to zero
this.carouselHeight = 0;
}
// initial set up
setup () {
this.resize();
window.addEventListener("resize", this.resize.bind(this));
}
}
Hopefully this helps someone!

Related

Reset the initial state of an element after some DOM manimulations (native js)

I want to save the initial state of my Element (with all childs) and after that do some actions ( initialize scrollbar plugin ) and after the window resize event I want to clear these changed values to the saved "pure" element and after that re-initialize the scrollbar plugin (I just want to do a scrollbar element responsive with the different views).
I tried to use .cloneNode(true), jquery.clone(true) and jquery.replaceWith() but it doesn't work for me becase these methods somehow keep all chages that was done before. So I can't get the saved initial HTML.
Should I user outerHTML maybe? Thanks for any help.
var GLOBAL_rawScrollBarNode;
var GLOBAL_scrollBarParent;
$(document).ready(function($){
GLOBAL_rawScrollBarNode = $('#scrollbar3').clone();
GLOBAL_scrollBarParent = $('.portfolio-scroll-box')[0];
portfolioScrollbarOfDeviceWidth();
$(window).resize(function(){
portfolioScrollbarOfDeviceWidth();
});
function portfolioScrollbarOfDeviceWidth() {
var documentWidth = document.documentElement.clientWidth;
//GLOBAL_scrollBarParent.removeChild(document.getElementById('scrollbar3'));
//GLOBAL_scrollBarParent.appendChild(GLOBAL_rawScrollBarNode);
GLOBAL_scrollBarParent.replaceWith(GLOBAL_rawScrollBarNode[0])
var $scrollBarEl = $('#scrollbar3');
setTimeout(() => {
if (documentWidth < 942) {
console.log("XXX")
var bar = $scrollBarEl.tinyscrollbar({ axis: 'x',sizethumb: 135 });
console.log(bar);
} else {
console.log("YYY")
var bar = $scrollBarEl.tinyscrollbar({ axis: 'y',sizethumb: 135 });
console.log(bar);
}
}, 2000)
}
P.S. in case of the outerHTML of the saved ELEMENT and after that assign it like innerHTML to it's parent - it works fine. But I want to know how this issue can be resolved in other more elegant way :)

getBoundingClientRect in each: undefined is not a function

So I am trying to call some functions when fullscreen sections are in the viewport. Let's say I have 7 sections, then I want something to happen when a certain section is inside the viewport (I have a function that snaps the sections into the viewport so there can never be multiple sections in the viewport, but I am trying to find out which section is visible in the viewport).
Here is a fiddle: http://jsfiddle.net/h7Hb7/2/
function isInViewport() {
$("section").each(function () {
var $this = $(this),
wHeight = $(window).height(),
rect = $this.getBoundingClientRect(); // Error in console
// Borrowed from http://stackoverflow.com/a/7557433/5628
if (rect.top >= 0 && rect.bottom <= wHeight) {
console.log($this.attr("id") + "in viewport");
}
});
}
$(window).scroll(function () {
// Other functions are called inside the setTimeout function, can't remove
clearTimeout($.data(this, "scrollTimer"));
$.data(this, "scrollTimer", setTimeout(function () {
isInViewport();
}, 1200));
});
I don't know where to start looking but I am guessing it's to do with the each function. Is it the each function that poses a problem? It can't be a CSS issue, because the problem occurs on scroll when the CSS has already loaded.
You could stick with jQuery and use the array [] notation ie:
var myClient = $(currentGrid)[0].getBoundingClientRect();
alert(myClient.top)
jQuery object doesn't have getBoundingClientRect method, you should get the HTMLElement object and then call the method or:
this.getBoundingClientRect();
As a suggestion, if using a plugin is an option, you can consider using the jquery.inview plugin.
You can pass event through function and use
e.target.getBoundingClientRect() function. It will Work

jQuery infinite scrolling scrolling in fixed div

Background
I am trying to create an infinite scrolling table inside a fixed position div. The problem is that all the solutions I come across use the window height and document scrollTop to calculate if the user has scrolled to the bottom of the screen.
Problem
I have tried to create a jQuery plugin that can calculate if a user has scrolled to the bottom of a fixed div with overflow: scroll; set.
My approach has been to create a wrapper div (the div with a fixed position and overflow: scroll) that wraps the table, I also place another div at the bottom of the table. I then try calculate if the wrapper.scrollTop() is greater than the bottom div position.top every time the wrapper is scrolled. I then load the new records and append them to the table body.
$.fn.isScrolledTo = function () {
var element = $(this);
var bottom = element.find('.bottom');
$(element).scroll(function () {
if (element.scrollTop() >= bottom.position().top) {
var tableBody = element.find("tbody");
tableBody.append(tableBody.html());
}
});
};
$('.fixed').isScrolledTo();
See Example http://jsfiddle.net/leviputna/v4q3a/
Question
Clearly my current example is not correct. My question is how to I detect when a user has scrolled to the bottom of a fixed div with overflow:scroll set?
Using the bottom element is a bit clunky, I think. Instead, why not use the scrollHeight and height to test once the scrollable area has run out.
$.fn.isScrolledTo = function () {
var element = this,
tableBody = this.find("tbody");
element.scroll(function(){
if( element.scrollTop() >= element[0].scrollHeight-element.height()){
tableBody.append(tableBody.html());
}
});
};
$('.fixed').isScrolledTo();
EDIT (12/30/14):
A DRYer version of the plugin might be much more re-usable:
$.fn.whenScrolledToBottom = function (cback_fxn) {
this.on('scroll',this,function(){
if( ev.data.scrollTop() >= ev.data[0].scrollHeight - ev.data.height()){
return cback_fxn.apply(ev.data, arguments)
}
});
};
Plugin Usage:
var $fixed = $('.fixed'),
$tableBody = $fixed.find("tbody");
$fixed.whenScrolledToBottom(function(){
// Load more data..
$tableBody.append($tableBody.html());
});
I have modified your code to handle the scroll event with a timer threshold:
$.fn.isScrolledTo = function () {
var element = $(this);
var bottom = element.find('.bottom');
$(element).scroll(function(){
if (this.timer) clearTimeout(this.timer);
this.timer=setTimeout(function(){
if( element.scrollTop() >= bottom.position().top){
var tableBody = element.find("tbody");
tableBody.append(tableBody.html());
}
},300);
});
};
$('.fixed').isScrolledTo();
The issue you are having is that as you scroll, new scroll event is being generated. Your code might have other issues, but this is a start.

Get divs within viewport inside a wrapper div

Is there a way to get elements which is:
Inside a div with overflow: scroll
Is in viewport
Just like the following picture, where active div (5,6,7,8,9) is orange, and the others is green (1-4 and >10) :
I just want the mousewheel event to add "active" class to div 5,6,7,8,9 (currently in viewport). View my JSFiddle
$('.wrapper').bind('mousewheel', function (e) {
//addClass 'active' here
});
You could do something like this. I would have re-factored it, but only to show the concept.
Firstly I would attach this to scroll event and not mousewheel. There are those among us that likes to use keyboard for scrolling, and you also have the case of dragging the scrollbar. ;) You also have the case of touch devices.
Note that with this I have set overflow:auto; on wrapper, thus no bottom scroll-bar.
With bottom scrollbar you would either have to live with it becoming tagged as in-view a tad to early, or tumble into the world of doing a cross-browser calculating of IE's clientHeight. But the code should hopefully be OK as a starter.
»»Fiddle««
function isView(wrp, elm)
{
var wrpH = $(wrp).height(),
elmH = $(elm).height(),
elmT = $(elm).offset().top;
return elmT >= 0 &&
elmT + elmH < wrpH;
}
$('.wrapper').bind('scroll', function (e) {
$('div.box').each(function(i, e) {
if (isView(".wrapper", this)) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
});
Note that you should likely refactor in such a way that .wrapper height is only retrieved once per invocation, or if it is static, at page load etc.
Update; a modified version of isView(). Taking position of container into account. This time looking at dolphins in the pool.
»»Fiddle««
function isView(pool, dolphin) {
var poolT = pool.offset().top,
poolH = pool.height(),
dolpH = dolphin.height(),
dolpT = dolphin.offset().top - poolT;
return dolpT >= 0 && dolpT + dolpH <= poolH;
}

How to listen for layout changes on a specific HTML element?

What are some techniques for listening for layout changes in modern browsers? window.resize won't work because it only fires when the entire window is resized, not when content changes cause reflow.
Specifically, I'd like to know when:
An element's available width changes.
The total height consumed by the in-flow children of an element changes.
There are no native events to hook into for this. You need to set a timer and poll this element's dimensions in your own code.
Here's the basic version. It polls every 100ms. I'm not sure how you want to check the children's height. This assumes they'll just make their wrapper taller.
var origHeight = 0;
var origWidth = 0;
var timer1;
function testSize() {
var $target = $('#target')
if(origHeight==0) {
origWidth = $target.outerWidth();
origHeight = $target.outerHeight();
}
else {
if(origWidth != $target.outerWidth() || origHeight = $target.outerHeight()) {
alert("change");
}
origWidth = $target.outerWidth();
origHeight = $target.outerHeight();
timer1= window.setTimeout(function(){ testSize() }),100)
}
}
New browsers now have ResizeObserver, which fires when the dimensions of an element's content box or border box are changed.
const observer = new ResizeObserver(entries => {
const entry = entries[0];
console.log('contentRect', entry.contentRect);
// do other work here…
});
observer.observe(element);
From a similar question How to know when an DOM element moves or is resized, there is a jQuery plugin from Ben Alman that does just this. This plugin uses the same polling approach outlined in Diodeus's answer.
Example from the plugin page:
// Well, try this on for size!
$("#unicorns").resize(function(e){
// do something when #unicorns element resizes
});

Categories