Added stagger option. More refactoring. Working home page demo again.

pull/111/head
Glen Cheney 8 years ago
parent efc1f8087a
commit 37e6a86ff3

@ -32,7 +32,7 @@
"indent": 2,
// Prohibit use of a variable before it is defined.
"latedef": true,
"latedef": "nofunc",
// Enforce line length to 100 characters
"maxlen": 100,

1064
dist/shuffle.js vendored

File diff suppressed because it is too large Load Diff

@ -1,161 +1,201 @@
'use strict';
var DEMO = (function ($) {
'use strict';
var $grid = $('#grid'),
$filterOptions = $('.filter-options'),
$sizer = $grid.find('.shuffle__sizer'),
init = function () {
// None of these need to be executed synchronously
setTimeout(function () {
listen();
setupFilters();
setupSorting();
setupSearching();
}, 100);
// You can subscribe to custom events.
// shrink, shrunk, filter, filtered, sorted, load, done
$grid.on('loading.shuffle done.shuffle layout.shuffle', function (evt, shuffle) {
// Make sure the browser has a console
if (window.console && window.console.log && typeof window.console.log === 'function') {
console.log('Shuffle:', evt.type);
}
});
// instantiate the plugin
$grid.shuffle({
itemSelector: '.picture-item',
sizer: $sizer,
});
// Destroy it! o_O
// $grid.shuffle('destroy');
},
// Set up button clicks
setupFilters = function () {
var $btns = $filterOptions.children();
$btns.on('click', function () {
var $this = $(this),
isActive = $this.hasClass('active'),
group = isActive ? 'all' : $this.data('group');
// Hide current label, show current label in title
if (!isActive) {
$('.filter-options .active').removeClass('active');
}
$this.toggleClass('active');
// Filter elements
$grid.shuffle('shuffle', group);
});
$btns = null;
},
setupSorting = function () {
// Sorting options
$('.sort-options').on('change', function () {
var sort = this.value,
opts = {};
// We're given the element wrapped in jQuery
if (sort === 'date-created') {
opts = {
reverse: true,
by: function ($el) {
return $el.data('date-created');
},
};
} else if (sort === 'title') {
opts = {
by: function ($el) {
return $el.data('title').toLowerCase();
},
};
}
// Filter elements
$grid.shuffle('sort', opts);
});
},
setupSearching = function () {
// Advanced filtering
$('.js-shuffle-search').on('keyup change', function () {
var val = this.value.toLowerCase();
$grid.shuffle('shuffle', function ($el, shuffle) {
// Only search elements in the current group
if (shuffle.group !== 'all' && $.inArray(shuffle.group, $el.data('groups')) === -1) {
return false;
}
var text = $.trim($el.find('.picture-item__title').text()).toLowerCase();
return text.indexOf(val) !== -1;
});
});
},
// Re layout shuffle when images load. This is only needed
// below 768 pixels because the .picture-item height is auto and therefore
// the height of the picture-item is dependent on the image
// I recommend using imagesloaded to determine when an image is loaded
// but that doesn't support IE7
listen = function () {
var debouncedLayout = $.throttle(300, function () {
$grid.shuffle('update');
});
// Get all images inside shuffle
$grid.find('img').each(function () {
var proxyImage;
// Image already loaded
if (this.complete && this.naturalWidth !== undefined) {
return;
}
// If none of the checks above matched, simulate loading on detached element.
proxyImage = new Image();
$(proxyImage).on('load', function () {
$(this).off('load');
debouncedLayout();
});
proxyImage.src = this.src;
});
// Because this method doesn't seem to be perfect.
setTimeout(function () {
debouncedLayout();
}, 500);
};
return {
init: init,
};
}(jQuery));
var Shuffle = window.Shuffle;
// $(document).ready(function() {
// DEMO.init();
// });
var Demo = function (element) {
this.element = element;
var Shuffle = window.Shuffle;
// Log out events.
this.addShuffleEventListeners();
var Demo = function () {
var element = document.getElementById('grid');
this.shuffle = new Shuffle(element, {
itemSelector: '.picture-item',
sizer: element.querySelector('.shuffle__sizer'),
});
this._activeFilters = [];
this.addFilterButtons();
this.addSorting();
this.addSearchFilter();
this.listenForImageLoads();
this.mode = 'exclusive';
};
Demo.prototype.toArray = function (arrayLike) {
return Array.prototype.slice.call(arrayLike);
};
Demo.prototype.toggleMode = function () {
if (this.mode === 'additive') {
this.mode = 'exclusive';
} else {
this.mode = 'additive';
}
};
/**
* Shuffle uses the CustomEvent constructor to dispatch events. You can listen
* for them like you normally would (with jQuery for example). The extra event
* data is in the `detail` property.
*/
Demo.prototype.addShuffleEventListeners = function () {
var handler = function (event) {
console.log('type: %s', event.type, 'detail:', event.detail);
};
this.element.addEventListener(Shuffle.EventType.LOADING, handler, false);
this.element.addEventListener(Shuffle.EventType.DONE, handler, false);
this.element.addEventListener(Shuffle.EventType.LAYOUT, handler, false);
this.element.addEventListener(Shuffle.EventType.REMOVED, handler, false);
};
Demo.prototype.addFilterButtons = function () {
var options = document.querySelector('.filter-options');
if (!options) {
return;
}
var filterButtons = this.toArray(
options.children
);
filterButtons.forEach(function (button) {
button.addEventListener('click', this._handleFilterClick.bind(this), false);
}, this);
};
Demo.prototype._handleFilterClick = function (evt) {
var btn = evt.currentTarget;
var isActive = btn.classList.contains('active');
var btnGroup = btn.getAttribute('data-group');
// You don't need _both_ of these modes. This is only for the demo.
// For this custom 'additive' mode in the demo, clicking on filter buttons
// doesn't remove any other filters.
if (this.mode === 'additive') {
// If this button is already active, remove it from the list of filters.
if (isActive) {
this._activeFilters.splice(this._activeFilters.indexOf(btnGroup));
} else {
this._activeFilters.push(btnGroup);
}
btn.classList.toggle('active');
// Filter elements
this.shuffle.filter(this._activeFilters);
// 'exclusive' mode lets only one filter button be active at a time.
} else {
this._removeActiveClassFromChildren(btn.parentNode);
var filterGroup;
if (isActive) {
btn.classList.remove('active');
filterGroup = Shuffle.ALL_ITEMS;
} else {
btn.classList.add('active');
filterGroup = btnGroup;
}
this.shuffle.filter(filterGroup);
}
};
Demo.prototype._removeActiveClassFromChildren = function (parent) {
var children = parent.children;
for (var i = children.length - 1; i >= 0; i--) {
children[i].classList.remove('active');
}
};
Demo.prototype.addSorting = function () {
var menu = document.querySelector('.sort-options');
if (!menu) {
return;
}
menu.addEventListener('change', this._handleSortChange.bind(this));
};
Demo.prototype._handleSortChange = function (evt) {
var value = evt.target.value;
var options = {};
function sortByDate(element) {
return element.getAttribute('data-created');
}
function sortByTitle(element) {
return element.getAttribute('data-title').toLowerCase();
}
if (value === 'date-created') {
options = {
reverse: true,
by: sortByDate,
};
} else if (value === 'title') {
options = {
by: sortByTitle,
};
}
this.shuffle.sort(options);
};
// Advanced filtering
Demo.prototype.addSearchFilter = function () {
var searchInput = document.querySelector('.js-shuffle-search');
if (!searchInput) {
return;
}
searchInput.addEventListener('keyup', this._handleSearchKeyup.bind(this));
};
Demo.prototype._handleSearchKeyup = function (evt) {
var searchText = evt.target.value.toLowerCase();
this.shuffle.filter(function (element, shuffle) {
// Get the item's groups.
var groups = JSON.parse(element.getAttribute('data-groups'));
// Only search elements in the current group
if (shuffle.group !== 'all' && groups.indexOf(shuffle.group) === -1) {
return false;
}
var title = element.querySelector('.picture-item__title');
var titleText = title.textContent.toLowerCase().trim();
return titleText.indexOf(searchText) !== -1;
});
};
/**
* Re-layout shuffle when images load. This is only needed below 768 pixels
* because the .picture-item height is auto and therefore the height of the
* picture-item is dependent on the image. I recommend using imagesloaded by
* desandro to determine when all your images have loaded.
*/
Demo.prototype.listenForImageLoads = function () {
var imgs = this.element.querySelectorAll('img');
var handler = function () {
this.shuffle.update();
}.bind(this);
for (var i = imgs.length - 1; i >= 0; i--) {
imgs[i].addEventListener('load', handler, false);
}
};
document.addEventListener('DOMContentLoaded', function () {
new Demo();
window.demo = new Demo(document.getElementById('grid'));
});

@ -0,0 +1,18 @@
'use strict';
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
export default function assign(target) {
var output = Object(target);
for (var i = 1, length = arguments.length; i < length; i++) {
var source = arguments[i];
if (source !== undefined && source !== null) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
output[key] = source[key];
}
}
}
}
return output;
}

@ -0,0 +1,12 @@
let element = document.body || document.documentElement;
let e = document.createElement('div');
e.style.cssText = 'width:10px;padding:2px;box-sizing:border-box;';
element.appendChild(e);
let width = window.getComputedStyle(e, null).width;
let ret = width === '10px';
element.removeChild(e);
export default ret;

@ -0,0 +1,35 @@
'use strict';
import getNumber from './get-number';
import COMPUTED_SIZE_INCLUDES_PADDING from './computed-size';
/**
* Retrieve the computed style for an element, parsed as a float.
* @param {Element} element Element to get style for.
* @param {string} style Style property.
* @param {CSSStyleDeclaration} [styles] Optionally include clean styles to
* use instead of asking for them again.
* @return {number} The parsed computed value or zero if that fails because IE
* will return 'auto' when the element doesn't have margins instead of
* the computed style.
* @private
*/
export default function getNumberStyle(element, style,
styles = window.getComputedStyle(element, null)) {
var value = getNumber(styles[style]);
// Support IE<=11 and W3C spec.
if (!COMPUTED_SIZE_INCLUDES_PADDING && style === 'width') {
value += getNumber(styles.paddingLeft) +
getNumber(styles.paddingRight) +
getNumber(styles.borderLeftWidth) +
getNumber(styles.borderRightWidth);
} else if (!COMPUTED_SIZE_INCLUDES_PADDING && style === 'height') {
value += getNumber(styles.paddingTop) +
getNumber(styles.paddingBottom) +
getNumber(styles.borderTopWidth) +
getNumber(styles.borderBottomWidth);
}
return value;
}

@ -10,7 +10,7 @@ export default function getNumber(value) {
let str = value && value.toString();
let val = parseFloat(str);
if (val + 1 >= 0) {
return value;
return val;
}
return 0;

@ -21,7 +21,7 @@ class ShuffleItem {
init() {
this.addClasses([Classes.SHUFFLE_ITEM, Classes.FILTERED]);
this._applyCss(ShuffleItem.css);
this.applyCss(ShuffleItem.css);
this.scale = ShuffleItem.Scale.VISIBLE;
this.point = new Point();
}
@ -32,11 +32,28 @@ class ShuffleItem {
});
}
_applyCss(obj) {
removeClasses(classes) {
classes.forEach((className) => {
this.element.classList.remove(className);
});
}
applyCss(obj) {
Object.keys(obj).forEach((key) => {
this.element.style[key] = obj[key];
});
}
dispose() {
this.removeClasses([
Classes.CONCEALED,
Classes.FILTERED,
Classes.SHUFFLE_ITEM,
]);
this.element.removeAttribute('style');
this.element = null;
}
}
ShuffleItem.css = {

File diff suppressed because it is too large Load Diff

@ -1,5 +1,7 @@
'use strict';
import assign from './assign';
// http://stackoverflow.com/a/962890/373422
function randomize(array) {
var tmp;
@ -29,12 +31,16 @@ let defaults = {
// If true, this will skip the sorting and return a randomized order in the array
randomize: false,
// Determines which property of each item in the array is passed to the
// sorting method.
key: 'element',
};
// You can return `undefined` from the `by` function to revert to DOM order.
export default function sorter(arr, options) {
let opts = Object.assign({}, defaults, options);
let original = Array.from(arr);
let opts = assign({}, defaults, options);
let original = [].slice.call(arr);
let revert = false;
if (!arr.length) {
@ -47,7 +53,7 @@ export default function sorter(arr, options) {
// Sort the elements by the opts.by function.
// If we don't have opts.by, default to DOM order
if (typeof options.by === 'function') {
if (typeof opts.by === 'function') {
arr.sort(function (a, b) {
// Exit early if we already know we want to revert
@ -55,8 +61,8 @@ export default function sorter(arr, options) {
return 0;
}
let valA = opts.by(a);
let valB = opts.by(b);
let valA = opts.by(a[opts.key]);
let valB = opts.by(b[opts.key]);
// If both values are undefined, use the DOM order
if (valA === undefined && valB === undefined) {

@ -0,0 +1,39 @@
'use strict';
// Underscore's throttle method.
export default function(func, wait, options) {
var _this;
var args;
var result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(_this, args);
if (!timeout) _this = args = null;
};
return function () {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
_this = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(_this, args);
if (!timeout) _this = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
}

@ -618,22 +618,22 @@ describe('Shuffle.js', function() {
document.body.appendChild(div);
expect(window.Shuffle._getOuterWidth(div, false)).toBe(100);
expect(window.Shuffle._getOuterWidth(div, true)).toBe(100);
expect(window.Shuffle.getSize(div, false).width).toBe(100);
expect(window.Shuffle.getSize(div, true).width).toBe(100);
expect(window.Shuffle._getOuterHeight(div, false)).toBe(100);
expect(window.Shuffle._getOuterHeight(div, true)).toBe(100);
expect(window.Shuffle.getSize(div, false).height).toBe(100);
expect(window.Shuffle.getSize(div, true).height).toBe(100);
div.style.marginLeft = '10px';
div.style.marginRight = '20px';
div.style.marginTop = '30px';
div.style.marginBottom = '40px';
expect(window.Shuffle._getOuterWidth(div, false)).toBe(100);
expect(window.Shuffle._getOuterWidth(div, true)).toBe(130);
expect(window.Shuffle.getSize(div, false).width).toBe(100);
expect(window.Shuffle.getSize(div, true).width).toBe(130);
expect(window.Shuffle._getOuterHeight(div, false)).toBe(100);
expect(window.Shuffle._getOuterHeight(div, true)).toBe(170);
expect(window.Shuffle.getSize(div, false).height).toBe(100);
expect(window.Shuffle.getSize(div, true).height).toBe(170);
document.body.removeChild(div);
});

Loading…
Cancel
Save