diff --git a/_config.yml b/_config.yml index 21aaff5..c6f89f5 100644 --- a/_config.yml +++ b/_config.yml @@ -28,6 +28,8 @@ exclude: - .gitignore # these are the files and directories that jekyll will exclude from the build +feedback_subject_line: Jekyll documentation theme + feedback_email: tomjohnson1492@gmail.com # used as a contact email for the Feedback link in the top navigation bar @@ -99,4 +101,4 @@ description: "Intended as a documentation theme based on Jekyll for technical wr # the description is used in the feed.xml file # needed for sitemap.xml file only -url: +url: http://idratherbewriting.com diff --git a/_data/sidebars/mydoc_sidebar.yml b/_data/sidebars/mydoc_sidebar.yml index 0f4118c..f278e60 100644 --- a/_data/sidebars/mydoc_sidebar.yml +++ b/_data/sidebars/mydoc_sidebar.yml @@ -244,6 +244,14 @@ entries: url: /mydoc_faq_layout.html output: web, pdf + - title: Scroll layout + url: /mydoc_scroll.html + output: web, pdf + + - title: Shuffle layout + url: /mydoc_shuffle.html + output: web, pdf + - title: Troubleshooting output: web, pdf diff --git a/_includes/feedback.html b/_includes/feedback.html index 7ac865f..d26268b 100644 --- a/_includes/feedback.html +++ b/_includes/feedback.html @@ -1 +1 @@ -
Send Feedback About This Page
\ No newline at end of file +
  • Feedback
  • diff --git a/_includes/initialize_shuffle.html b/_includes/initialize_shuffle.html new file mode 100644 index 0000000..9a0f048 --- /dev/null +++ b/_includes/initialize_shuffle.html @@ -0,0 +1,130 @@ + + + + + + + + + + + diff --git a/js/jquery.ba-throttle-debounce.min.js b/js/jquery.ba-throttle-debounce.min.js new file mode 100644 index 0000000..0720550 --- /dev/null +++ b/js/jquery.ba-throttle-debounce.min.js @@ -0,0 +1,9 @@ +/* + * jQuery throttle / debounce - v1.1 - 3/7/2010 + * http://benalman.com/projects/jquery-throttle-debounce-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); \ No newline at end of file diff --git a/js/jquery.localScroll.min.js b/js/jquery.localScroll.min.js new file mode 100644 index 0000000..48a6e16 --- /dev/null +++ b/js/jquery.localScroll.min.js @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT + * @author Ariel Flesler + * @version 1.3.5 + */ +;(function(a){if(typeof define==='function'&&define.amd){define(['jquery'],a)}else{a(jQuery)}}(function($){var g=location.href.replace(/#.*/,'');var h=$.localScroll=function(a){$('body').localScroll(a)};h.defaults={duration:1000,axis:'y',event:'click',stop:true,target:window};$.fn.localScroll=function(a){a=$.extend({},h.defaults,a);if(a.hash&&location.hash){if(a.target)window.scrollTo(0,0);scroll(0,location,a)}return a.lazy?this.on(a.event,'a,area',function(e){if(filter.call(this)){scroll(e,this,a)}}):this.find('a,area').filter(filter).bind(a.event,function(e){scroll(e,this,a)}).end().end();function filter(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,'')==g&&(!a.filter||$(this).is(a.filter))}};h.hash=function(){};function scroll(e,a,b){var c=a.hash.slice(1),elem=document.getElementById(c)||document.getElementsByName(c)[0];if(!elem)return;if(e)e.preventDefault();var d=$(b.target);if(b.lock&&d.is(':animated')||b.onBefore&&b.onBefore(e,elem,d)===false)return;if(b.stop)d._scrollable().stop(true);if(b.hash){var f=elem.id===c?'id':'name',$a=$(' ').attr(f,c).css({position:'absolute',top:$(window).scrollTop(),left:$(window).scrollLeft()});elem[f]='';$('body').prepend($a);location.hash=a.hash;$a.remove();elem[f]=c}d.scrollTo(elem,b).trigger('notify.serialScroll',[elem])};return h})); \ No newline at end of file diff --git a/js/jquery.scrollTo.min.js b/js/jquery.scrollTo.min.js new file mode 100644 index 0000000..5f5e01e --- /dev/null +++ b/js/jquery.scrollTo.min.js @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT + * @author Ariel Flesler + * @version 1.4.14 + */ +;(function(k){'use strict';k(['jquery'],function($){var j=$.scrollTo=function(a,b,c){return $(window).scrollTo(a,b,c)};j.defaults={axis:'xy',duration:0,limit:!0};j.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(f,g,h){if(typeof g=='object'){h=g;g=0}if(typeof h=='function')h={onAfter:h};if(f=='max')f=9e9;h=$.extend({},j.defaults,h);g=g||h.duration;h.queue=h.queue&&h.axis.length>1;if(h.queue)g/=2;h.offset=both(h.offset);h.over=both(h.over);return this._scrollable().each(function(){if(f==null)return;var d=this,$elem=$(d),targ=f,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=win?$(targ):$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}var e=$.isFunction(h.offset)&&h.offset(d,targ)||h.offset;$.each(h.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=j.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(h.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=e[pos]||0;if(h.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*h.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(h.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&h.queue){if(old!=attr[key])animate(h.onAfterFirst);delete attr[key]}});animate(h.onAfter);function animate(a){$elem.animate(attr,g,h.easing,a&&function(){a.call(this,targ,h)})}}).end()};j.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}return j})}(typeof define==='function'&&define.amd?define:function(a,b){if(typeof module!=='undefined'&&module.exports){module.exports=b(require('jquery'))}else{b(jQuery)}})); \ No newline at end of file diff --git a/js/jquery.shuffle.min.js b/js/jquery.shuffle.min.js new file mode 100644 index 0000000..d103127 --- /dev/null +++ b/js/jquery.shuffle.min.js @@ -0,0 +1,1588 @@ +/*! + * Shuffle.js by @Vestride + * Categorize, sort, and filter a responsive grid of items. + * Dependencies: jQuery 1.9+, Modernizr 2.6.2+ + * @license MIT license + * @version 3.0.0 + */ + +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-cssclasses-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes + */ +window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a){var e=a[d];if(!C(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.csstransforms=function(){return!!F("transform")},q.csstransforms3d=function(){var a=!!F("perspective");return a&&"webkitPerspective"in g.style&&w("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},q.csstransitions=function(){return F("transition")};for(var G in q)y(q,G)&&(v=G.toLowerCase(),e[v]=q[G](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document); + +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', 'modernizr'], factory); + } else { + window.Shuffle = factory(window.jQuery, window.Modernizr); + } +})(function($, Modernizr, undefined) { + +'use strict'; + + +// Validate Modernizr exists. +// Shuffle requires `csstransitions`, `csstransforms`, `csstransforms3d`, +// and `prefixed` to exist on the Modernizr object. +if (typeof Modernizr !== 'object') { + throw new Error('Shuffle.js requires Modernizr.\n' + + 'http://vestride.github.io/Shuffle/#dependencies'); +} + + +/** + * Returns css prefixed properties like `-webkit-transition` or `box-sizing` + * from `transition` or `boxSizing`, respectively. + * @param {(string|boolean)} prop Property to be prefixed. + * @return {string} The prefixed css property. + */ +function dashify( prop ) { + if (!prop) { + return ''; + } + + // Replace upper case with dash-lowercase, + // then fix ms- prefixes because they're not capitalized. + return prop.replace(/([A-Z])/g, function( str, m1 ) { + return '-' + m1.toLowerCase(); + }).replace(/^ms-/,'-ms-'); +} + +// Constant, prefixed variables. +var TRANSITION = Modernizr.prefixed('transition'); +var TRANSITION_DELAY = Modernizr.prefixed('transitionDelay'); +var TRANSITION_DURATION = Modernizr.prefixed('transitionDuration'); + +// Note(glen): Stock Android 4.1.x browser will fail here because it wrongly +// says it supports non-prefixed transitions. +// https://github.com/Modernizr/Modernizr/issues/897 +var TRANSITIONEND = { + 'WebkitTransition' : 'webkitTransitionEnd', + 'transition' : 'transitionend' +}[ TRANSITION ]; + +var TRANSFORM = Modernizr.prefixed('transform'); +var CSS_TRANSFORM = dashify(TRANSFORM); + +// Constants +var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions; +var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d; +var SHUFFLE = 'shuffle'; +var COLUMN_THRESHOLD = 0.3; + +// Configurable. You can change these constants to fit your application. +// The default scale and concealed scale, however, have to be different values. +var ALL_ITEMS = 'all'; +var FILTER_ATTRIBUTE_KEY = 'groups'; +var DEFAULT_SCALE = 1; +var CONCEALED_SCALE = 0.001; + + +// Underscore's throttle function. +function throttle(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + options = options || {}; + var later = function() { + previous = options.leading === false ? 0 : $.now(); + timeout = null; + result = func.apply(context, args); + context = args = null; + }; + return function() { + var now = $.now(); + if (!previous && options.leading === false) { + previous = now; + } + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; +} + +function each(obj, iterator, context) { + for (var i = 0, length = obj.length; i < length; i++) { + if (iterator.call(context, obj[i], i, obj) === {}) { + return; + } + } +} + +function defer(fn, context, wait) { + return setTimeout( $.proxy( fn, context ), wait ); +} + +function arrayMax( array ) { + return Math.max.apply( Math, array ); +} + +function arrayMin( array ) { + return Math.min.apply( Math, array ); +} + + +/** + * Always returns a numeric value, given a value. + * @param {*} value Possibly numeric value. + * @return {number} `value` or zero if `value` isn't numeric. + * @private + */ +function getNumber(value) { + return $.isNumeric(value) ? value : 0; +} + + +/** + * Represents a coordinate pair. + * @param {number} [x=0] X. + * @param {number} [y=0] Y. + */ +var Point = function(x, y) { + this.x = getNumber( x ); + this.y = getNumber( y ); +}; + + +/** + * Whether two points are equal. + * @param {Point} a Point A. + * @param {Point} b Point B. + * @return {boolean} + */ +Point.equals = function(a, b) { + return a.x === b.x && a.y === b.y; +}; + + +// Used for unique instance variables +var id = 0; +var $window = $( window ); + + +/** + * Categorize, sort, and filter a responsive grid of items. + * + * @param {Element} element An element which is the parent container for the grid items. + * @param {Object} [options=Shuffle.options] Options object. + * @constructor + */ +var Shuffle = function( element, options ) { + options = options || {}; + $.extend( this, Shuffle.options, options, Shuffle.settings ); + + this.$el = $(element); + this.element = element; + this.unique = 'shuffle_' + id++; + + this._fire( Shuffle.EventType.LOADING ); + this._init(); + + // Dispatch the done event asynchronously so that people can bind to it after + // Shuffle has been initialized. + defer(function() { + this.initialized = true; + this._fire( Shuffle.EventType.DONE ); + }, this, 16); +}; + + +/** + * Events the container element emits with the .shuffle namespace. + * For example, "done.shuffle". + * @enum {string} + */ +Shuffle.EventType = { + LOADING: 'loading', + DONE: 'done', + LAYOUT: 'layout', + REMOVED: 'removed' +}; + + +/** @enum {string} */ +Shuffle.ClassName = { + BASE: SHUFFLE, + SHUFFLE_ITEM: 'shuffle-item', + FILTERED: 'filtered', + CONCEALED: 'concealed' +}; + + +// Overrideable options +Shuffle.options = { + group: ALL_ITEMS, // Initial filter group. + speed: 250, // Transition/animation speed (milliseconds). + easing: 'ease-out', // CSS easing function to use. + itemSelector: '', // e.g. '.picture-item'. + sizer: null, // Sizer element. Use an element to determine the size of columns and gutters. + gutterWidth: 0, // A static number or function that tells the plugin how wide the gutters between columns are (in pixels). + columnWidth: 0, // A static number or function that returns a number which tells the plugin how wide the columns are (in pixels). + delimeter: null, // If your group is not json, and is comma delimeted, you could set delimeter to ','. + buffer: 0, // Useful for percentage based heights when they might not always be exactly the same (in pixels). + initialSort: null, // Shuffle can be initialized with a sort object. It is the same object given to the sort method. + throttle: throttle, // By default, shuffle will throttle resize events. This can be changed or removed. + throttleTime: 300, // How often shuffle can be called on resize (in milliseconds). + sequentialFadeDelay: 150, // Delay between each item that fades in when adding items. + supported: CAN_TRANSITION_TRANSFORMS // Whether to use transforms or absolute positioning. +}; + + +// Not overrideable +Shuffle.settings = { + useSizer: false, + itemCss : { // default CSS for each item + position: 'absolute', + top: 0, + left: 0, + visibility: 'visible' + }, + revealAppendedDelay: 300, + lastSort: {}, + lastFilter: ALL_ITEMS, + enabled: true, + destroyed: false, + initialized: false, + _animations: [], + styleQueue: [] +}; + + +// Expose for testing. +Shuffle.Point = Point; + + +/** + * Static methods. + */ + +/** + * If the browser has 3d transforms available, build a string with those, + * otherwise use 2d transforms. + * @param {Point} point X and Y positions. + * @param {number} scale Scale amount. + * @return {string} A normalized string which can be used with the transform style. + * @private + */ +Shuffle._getItemTransformString = function(point, scale) { + if ( HAS_TRANSFORMS_3D ) { + return 'translate3d(' + point.x + 'px, ' + point.y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)'; + } else { + return 'translate(' + point.x + 'px, ' + point.y + 'px) scale(' + scale + ')'; + } +}; + + +/** + * Retrieve the computed style for an element, parsed as a float. This should + * not be used for width or height values because jQuery mangles them and they + * are not precise enough. + * @param {Element} element Element to get style for. + * @param {string} style Style property. + * @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 + */ +Shuffle._getNumberStyle = function( element, style ) { + return Shuffle._getFloat( $( element ).css( style ) ); +}; + + +/** + * Parse a string as an integer. + * @param {string} value String integer. + * @return {number} The string as an integer or zero. + * @private + */ +Shuffle._getInt = function(value) { + return getNumber( parseInt( value, 10 ) ); +}; + +/** + * Parse a string as an float. + * @param {string} value String float. + * @return {number} The string as an float or zero. + * @private + */ +Shuffle._getFloat = function(value) { + return getNumber( parseFloat( value ) ); +}; + + +/** + * Returns the outer width of an element, optionally including its margins. + * The `offsetWidth` property must be used because having a scale transform + * on the element affects the bounding box. Sadly, Firefox doesn't return an + * integer value for offsetWidth (yet). + * @param {Element} element The element. + * @param {boolean} [includeMargins] Whether to include margins. Default is false. + * @return {number} The width. + */ +Shuffle._getOuterWidth = function( element, includeMargins ) { + var width = element.offsetWidth; + + // Use jQuery here because it uses getComputedStyle internally and is + // cross-browser. Using the style property of the element will only work + // if there are inline styles. + if ( includeMargins ) { + var marginLeft = Shuffle._getNumberStyle( element, 'marginLeft'); + var marginRight = Shuffle._getNumberStyle( element, 'marginRight'); + width += marginLeft + marginRight; + } + + return width; +}; + + +/** + * Returns the outer height of an element, optionally including its margins. + * @param {Element} element The element. + * @param {boolean} [includeMargins] Whether to include margins. Default is false. + * @return {number} The height. + */ +Shuffle._getOuterHeight = function( element, includeMargins ) { + var height = element.offsetHeight; + + if ( includeMargins ) { + var marginTop = Shuffle._getNumberStyle( element, 'marginTop'); + var marginBottom = Shuffle._getNumberStyle( element, 'marginBottom'); + height += marginTop + marginBottom; + } + + return height; +}; + + +/** + * Change a property or execute a function which will not have a transition + * @param {Element} element DOM element that won't be transitioned + * @param {Function} callback A function which will be called while transition + * is set to 0ms. + * @param {Object} [context] Optional context for the callback function. + * @private + */ +Shuffle._skipTransition = function( element, callback, context ) { + var duration = element.style[ TRANSITION_DURATION ]; + + // Set the duration to zero so it happens immediately + element.style[ TRANSITION_DURATION ] = '0ms'; // ms needed for firefox! + + callback.call( context ); + + // Force reflow + var reflow = element.offsetWidth; + // Avoid jshint warnings: unused variables and expressions. + reflow = null; + + // Put the duration back + element.style[ TRANSITION_DURATION ] = duration; +}; + + +/** + * Instance methods. + */ + +Shuffle.prototype._init = function() { + this.$items = this._getItems(); + + this.sizer = this._getElementOption( this.sizer ); + + if ( this.sizer ) { + this.useSizer = true; + } + + // Add class and invalidate styles + this.$el.addClass( Shuffle.ClassName.BASE ); + + // Set initial css for each item + this._initItems(); + + // Bind resize events + // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer + $window.on('resize.' + SHUFFLE + '.' + this.unique, this._getResizeFunction()); + + // Get container css all in one request. Causes reflow + var containerCSS = this.$el.css(['position', 'overflow']); + var containerWidth = Shuffle._getOuterWidth( this.element ); + + // Add styles to the container if it doesn't have them. + this._validateStyles( containerCSS ); + + // We already got the container's width above, no need to cause another reflow getting it again... + // Calculate the number of columns there will be + this._setColumns( containerWidth ); + + // Kick off! + this.shuffle( this.group, this.initialSort ); + + // The shuffle items haven't had transitions set on them yet + // so the user doesn't see the first layout. Set them now that the first layout is done. + if ( this.supported ) { + defer(function() { + this._setTransitions(); + this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing; + }, this); + } +}; + + +/** + * Returns a throttled and proxied function for the resize handler. + * @return {Function} + * @private + */ +Shuffle.prototype._getResizeFunction = function() { + var resizeFunction = $.proxy( this._onResize, this ); + return this.throttle ? + this.throttle( resizeFunction, this.throttleTime ) : + resizeFunction; +}; + + +/** + * Retrieve an element from an option. + * @param {string|jQuery|Element} option The option to check. + * @return {?Element} The plain element or null. + * @private + */ +Shuffle.prototype._getElementOption = function( option ) { + // If column width is a string, treat is as a selector and search for the + // sizer element within the outermost container + if ( typeof option === 'string' ) { + return this.$el.find( option )[0] || null; + + // Check for an element + } else if ( option && option.nodeType && option.nodeType === 1 ) { + return option; + + // Check for jQuery object + } else if ( option && option.jquery ) { + return option[0]; + } + + return null; +}; + + +/** + * Ensures the shuffle container has the css styles it needs applied to it. + * @param {Object} styles Key value pairs for position and overflow. + * @private + */ +Shuffle.prototype._validateStyles = function(styles) { + // Position cannot be static. + if ( styles.position === 'static' ) { + this.element.style.position = 'relative'; + } + + // Overflow has to be hidden + if ( styles.overflow !== 'hidden' ) { + this.element.style.overflow = 'hidden'; + } +}; + + +/** + * Filter the elements by a category. + * @param {string} [category] Category to filter by. If it's given, the last + * category will be used to filter the items. + * @param {ArrayLike} [$collection] Optionally filter a collection. Defaults to + * all the items. + * @return {jQuery} Filtered items. + * @private + */ +Shuffle.prototype._filter = function( category, $collection ) { + category = category || this.lastFilter; + $collection = $collection || this.$items; + + var set = this._getFilteredSets( category, $collection ); + + // Individually add/remove concealed/filtered classes + this._toggleFilterClasses( set.filtered, set.concealed ); + + // Save the last filter in case elements are appended. + this.lastFilter = category; + + // This is saved mainly because providing a filter function (like searching) + // will overwrite the `lastFilter` property every time its called. + if ( typeof category === 'string' ) { + this.group = category; + } + + return set.filtered; +}; + + +/** + * Returns an object containing the filtered and concealed elements. + * @param {string|Function} category Category or function to filter by. + * @param {ArrayLike.} $items A collection of items to filter. + * @return {!{filtered: jQuery, concealed: jQuery}} + * @private + */ +Shuffle.prototype._getFilteredSets = function( category, $items ) { + var $filtered = $(); + var $concealed = $(); + + // category === 'all', add filtered class to everything + if ( category === ALL_ITEMS ) { + $filtered = $items; + + // Loop through each item and use provided function to determine + // whether to hide it or not. + } else { + each($items, function( el ) { + var $item = $(el); + if ( this._doesPassFilter( category, $item ) ) { + $filtered = $filtered.add( $item ); + } else { + $concealed = $concealed.add( $item ); + } + }, this); + } + + return { + filtered: $filtered, + concealed: $concealed + }; +}; + + +/** + * Test an item to see if it passes a category. + * @param {string|Function} category Category or function to filter by. + * @param {jQuery} $item A single item, wrapped with jQuery. + * @return {boolean} Whether it passes the category/filter. + * @private + */ +Shuffle.prototype._doesPassFilter = function( category, $item ) { + if ( $.isFunction( category ) ) { + return category.call( $item[0], $item, this ); + + // Check each element's data-groups attribute against the given category. + } else { + var groups = $item.data( FILTER_ATTRIBUTE_KEY ); + var keys = this.delimeter && !$.isArray( groups ) ? + groups.split( this.delimeter ) : + groups; + return $.inArray(category, keys) > -1; + } +}; + + +/** + * Toggles the filtered and concealed class names. + * @param {jQuery} $filtered Filtered set. + * @param {jQuery} $concealed Concealed set. + * @private + */ +Shuffle.prototype._toggleFilterClasses = function( $filtered, $concealed ) { + $filtered + .removeClass( Shuffle.ClassName.CONCEALED ) + .addClass( Shuffle.ClassName.FILTERED ); + $concealed + .removeClass( Shuffle.ClassName.FILTERED ) + .addClass( Shuffle.ClassName.CONCEALED ); +}; + + +/** + * Set the initial css for each item + * @param {jQuery} [$items] Optionally specifiy at set to initialize + */ +Shuffle.prototype._initItems = function( $items ) { + $items = $items || this.$items; + $items.addClass([ + Shuffle.ClassName.SHUFFLE_ITEM, + Shuffle.ClassName.FILTERED + ].join(' ')); + $items.css( this.itemCss ).data('point', new Point()).data('scale', DEFAULT_SCALE); +}; + + +/** + * Updates the filtered item count. + * @private + */ +Shuffle.prototype._updateItemCount = function() { + this.visibleItems = this._getFilteredItems().length; +}; + + +/** + * Sets css transform transition on a an element. + * @param {Element} element Element to set transition on. + * @private + */ +Shuffle.prototype._setTransition = function( element ) { + element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' + + this.easing + ', opacity ' + this.speed + 'ms ' + this.easing; +}; + + +/** + * Sets css transform transition on a group of elements. + * @param {ArrayLike.} $items Elements to set transitions on. + * @private + */ +Shuffle.prototype._setTransitions = function( $items ) { + $items = $items || this.$items; + each($items, function( el ) { + this._setTransition( el ); + }, this); +}; + + +/** + * Sets a transition delay on a collection of elements, making each delay + * greater than the last. + * @param {ArrayLike.} $collection Array to iterate over. + */ +Shuffle.prototype._setSequentialDelay = function( $collection ) { + if ( !this.supported ) { + return; + } + + // $collection can be an array of dom elements or jquery object + each($collection, function( el, i ) { + // This works because the transition-property: transform, opacity; + el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * this.sequentialFadeDelay) + 'ms'; + }, this); +}; + + +Shuffle.prototype._getItems = function() { + return this.$el.children( this.itemSelector ); +}; + + +Shuffle.prototype._getFilteredItems = function() { + return this.$items.filter('.' + Shuffle.ClassName.FILTERED); +}; + + +Shuffle.prototype._getConcealedItems = function() { + return this.$items.filter('.' + Shuffle.ClassName.CONCEALED); +}; + + +/** + * Returns the column size, based on column width and sizer options. + * @param {number} containerWidth Size of the parent container. + * @param {number} gutterSize Size of the gutters. + * @return {number} + * @private + */ +Shuffle.prototype._getColumnSize = function( containerWidth, gutterSize ) { + var size; + + // If the columnWidth property is a function, then the grid is fluid + if ( $.isFunction( this.columnWidth ) ) { + size = this.columnWidth(containerWidth); + + // columnWidth option isn't a function, are they using a sizing element? + } else if ( this.useSizer ) { + size = Shuffle._getOuterWidth(this.sizer); + + // if not, how about the explicitly set option? + } else if ( this.columnWidth ) { + size = this.columnWidth; + + // or use the size of the first item + } else if ( this.$items.length > 0 ) { + size = Shuffle._getOuterWidth(this.$items[0], true); + + // if there's no items, use size of container + } else { + size = containerWidth; + } + + // Don't let them set a column width of zero. + if ( size === 0 ) { + size = containerWidth; + } + + return size + gutterSize; +}; + + +/** + * Returns the gutter size, based on gutter width and sizer options. + * @param {number} containerWidth Size of the parent container. + * @return {number} + * @private + */ +Shuffle.prototype._getGutterSize = function( containerWidth ) { + var size; + if ( $.isFunction( this.gutterWidth ) ) { + size = this.gutterWidth(containerWidth); + } else if ( this.useSizer ) { + size = Shuffle._getNumberStyle(this.sizer, 'marginLeft'); + } else { + size = this.gutterWidth; + } + + return size; +}; + + +/** + * Calculate the number of columns to be used. Gets css if using sizer element. + * @param {number} [theContainerWidth] Optionally specify a container width if it's already available. + */ +Shuffle.prototype._setColumns = function( theContainerWidth ) { + var containerWidth = theContainerWidth || Shuffle._getOuterWidth( this.element ); + var gutter = this._getGutterSize( containerWidth ); + var columnWidth = this._getColumnSize( containerWidth, gutter ); + var calculatedColumns = (containerWidth + gutter) / columnWidth; + + // Widths given from getComputedStyle are not precise enough... + if ( Math.abs(Math.round(calculatedColumns) - calculatedColumns) < COLUMN_THRESHOLD ) { + // e.g. calculatedColumns = 11.998876 + calculatedColumns = Math.round( calculatedColumns ); + } + + this.cols = Math.max( Math.floor(calculatedColumns), 1 ); + this.containerWidth = containerWidth; + this.colWidth = columnWidth; +}; + +/** + * Adjust the height of the grid + */ +Shuffle.prototype._setContainerSize = function() { + this.$el.css( 'height', this._getContainerSize() ); +}; + + +/** + * Based on the column heights, it returns the biggest one. + * @return {number} + * @private + */ +Shuffle.prototype._getContainerSize = function() { + return arrayMax( this.positions ); +}; + + +/** + * Fire events with .shuffle namespace + */ +Shuffle.prototype._fire = function( name, args ) { + this.$el.trigger( name + '.' + SHUFFLE, args && args.length ? args : [ this ] ); +}; + + +/** + * Zeros out the y columns array, which is used to determine item placement. + * @private + */ +Shuffle.prototype._resetCols = function() { + var i = this.cols; + this.positions = []; + while (i--) { + this.positions.push( 0 ); + } +}; + + +/** + * Loops through each item that should be shown and calculates the x, y position. + * @param {Array.} items Array of items that will be shown/layed out in order in their array. + * Because jQuery collection are always ordered in DOM order, we can't pass a jq collection. + * @param {boolean} [isOnlyPosition=false] If true this will position the items with zero opacity. + */ +Shuffle.prototype._layout = function( items, isOnlyPosition ) { + each(items, function( item ) { + this._layoutItem( item, !!isOnlyPosition ); + }, this); + + // `_layout` always happens after `_shrink`, so it's safe to process the style + // queue here with styles from the shrink method. + this._processStyleQueue(); + + // Adjust the height of the container. + this._setContainerSize(); +}; + + +/** + * Calculates the position of the item and pushes it onto the style queue. + * @param {Element} item Element which is being positioned. + * @param {boolean} isOnlyPosition Whether to position the item, but with zero + * opacity so that it can fade in later. + * @private + */ +Shuffle.prototype._layoutItem = function( item, isOnlyPosition ) { + var $item = $(item); + var itemData = $item.data(); + var currPos = itemData.point; + var currScale = itemData.scale; + var itemSize = { + width: Shuffle._getOuterWidth( item, true ), + height: Shuffle._getOuterHeight( item, true ) + }; + var pos = this._getItemPosition( itemSize ); + + // If the item will not change its position, do not add it to the render + // queue. Transitions don't fire when setting a property to the same value. + if ( Point.equals(currPos, pos) && currScale === DEFAULT_SCALE ) { + return; + } + + // Save data for shrink + itemData.point = pos; + itemData.scale = DEFAULT_SCALE; + + this.styleQueue.push({ + $item: $item, + point: pos, + scale: DEFAULT_SCALE, + opacity: isOnlyPosition ? 0 : 1, + skipTransition: isOnlyPosition, + callfront: function() { + if ( !isOnlyPosition ) { + $item.css( 'visibility', 'visible' ); + } + }, + callback: function() { + if ( isOnlyPosition ) { + $item.css( 'visibility', 'hidden' ); + } + } + }); +}; + + +/** + * Determine the location of the next item, based on its size. + * @param {{width: number, height: number}} itemSize Object with width and height. + * @return {Point} + * @private + */ +Shuffle.prototype._getItemPosition = function( itemSize ) { + var columnSpan = this._getColumnSpan( itemSize.width, this.colWidth, this.cols ); + + var setY = this._getColumnSet( columnSpan, this.cols ); + + // Finds the index of the smallest number in the set. + var shortColumnIndex = this._getShortColumn( setY, this.buffer ); + + // Position the item + var point = new Point( + Math.round( this.colWidth * shortColumnIndex ), + Math.round( setY[shortColumnIndex] )); + + // Update the columns array with the new values for each column. + // e.g. before the update the columns could be [250, 0, 0, 0] for an item + // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0]. + var setHeight = setY[shortColumnIndex] + itemSize.height; + var setSpan = this.cols + 1 - setY.length; + for ( var i = 0; i < setSpan; i++ ) { + this.positions[ shortColumnIndex + i ] = setHeight; + } + + return point; +}; + + +/** + * Determine the number of columns an items spans. + * @param {number} itemWidth Width of the item. + * @param {number} columnWidth Width of the column (includes gutter). + * @param {number} columns Total number of columns + * @return {number} + * @private + */ +Shuffle.prototype._getColumnSpan = function( itemWidth, columnWidth, columns ) { + var columnSpan = itemWidth / columnWidth; + + // If the difference between the rounded column span number and the + // calculated column span number is really small, round the number to + // make it fit. + if ( Math.abs(Math.round( columnSpan ) - columnSpan ) < COLUMN_THRESHOLD ) { + // e.g. columnSpan = 4.0089945390298745 + columnSpan = Math.round( columnSpan ); + } + + // Ensure the column span is not more than the amount of columns in the whole layout. + return Math.min( Math.ceil( columnSpan ), columns ); +}; + + +/** + * Retrieves the column set to use for placement. + * @param {number} columnSpan The number of columns this current item spans. + * @param {number} columns The total columns in the grid. + * @return {Array.} An array of numbers represeting the column set. + * @private + */ +Shuffle.prototype._getColumnSet = function( columnSpan, columns ) { + // The item spans only one column. + if ( columnSpan === 1 ) { + return this.positions; + + // The item spans more than one column, figure out how many different + // places it could fit horizontally. + // The group count is the number of places within the positions this block + // could fit, ignoring the current positions of items. + // Imagine a 2 column brick as the second item in a 4 column grid with + // 10px height each. Find the places it would fit: + // [10, 0, 0, 0] + // | | | + // * * * + // + // Then take the places which fit and get the bigger of the two: + // max([10, 0]), max([0, 0]), max([0, 0]) = [10, 0, 0] + // + // Next, find the first smallest number (the short column). + // [10, 0, 0] + // | + // * + // + // And that's where it should be placed! + } else { + var groupCount = columns + 1 - columnSpan; + var groupY = []; + + // For how many possible positions for this item there are. + for ( var i = 0; i < groupCount; i++ ) { + // Find the bigger value for each place it could fit. + groupY[i] = arrayMax( this.positions.slice( i, i + columnSpan ) ); + } + + return groupY; + } +}; + + +/** + * Find index of short column, the first from the left where this item will go. + * + * @param {Array.} positions The array to search for the smallest number. + * @param {number} buffer Optional buffer which is very useful when the height + * is a percentage of the width. + * @return {number} Index of the short column. + * @private + */ +Shuffle.prototype._getShortColumn = function( positions, buffer ) { + var minPosition = arrayMin( positions ); + for (var i = 0, len = positions.length; i < len; i++) { + if ( positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer ) { + return i; + } + } + return 0; +}; + + +/** + * Hides the elements that don't match our filter. + * @param {jQuery} $collection jQuery collection to shrink. + * @private + */ +Shuffle.prototype._shrink = function( $collection ) { + var $concealed = $collection || this._getConcealedItems(); + + each($concealed, function( item ) { + var $item = $(item); + var itemData = $item.data(); + + // Continuing would add a transitionend event listener to the element, but + // that listener would not execute because the transform and opacity would + // stay the same. + if ( itemData.scale === CONCEALED_SCALE ) { + return; + } + + itemData.scale = CONCEALED_SCALE; + + this.styleQueue.push({ + $item: $item, + point: itemData.point, + scale : CONCEALED_SCALE, + opacity: 0, + callback: function() { + $item.css( 'visibility', 'hidden' ); + } + }); + }, this); +}; + + +/** + * Resize handler. + * @private + */ +Shuffle.prototype._onResize = function() { + // If shuffle is disabled, destroyed, don't do anything + if ( !this.enabled || this.destroyed || this.isTransitioning ) { + return; + } + + // Will need to check height in the future if it's layed out horizontaly + var containerWidth = Shuffle._getOuterWidth( this.element ); + + // containerWidth hasn't changed, don't do anything + if ( containerWidth === this.containerWidth ) { + return; + } + + this.update(); +}; + + +/** + * Returns styles for either jQuery animate or transition. + * @param {Object} opts Transition options. + * @return {!Object} Transforms for transitions, left/top for animate. + * @private + */ +Shuffle.prototype._getStylesForTransition = function( opts ) { + var styles = { + opacity: opts.opacity + }; + + if ( this.supported ) { + styles[ TRANSFORM ] = Shuffle._getItemTransformString( opts.point, opts.scale ); + } else { + styles.left = opts.point.x; + styles.top = opts.point.y; + } + + return styles; +}; + + +/** + * Transitions an item in the grid + * + * @param {Object} opts options. + * @param {jQuery} opts.$item jQuery object representing the current item. + * @param {Point} opts.point A point object with the x and y coordinates. + * @param {number} opts.scale Amount to scale the item. + * @param {number} opts.opacity Opacity of the item. + * @param {Function} opts.callback Complete function for the animation. + * @param {Function} opts.callfront Function to call before transitioning. + * @private + */ +Shuffle.prototype._transition = function( opts ) { + var styles = this._getStylesForTransition( opts ); + this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop ); +}; + + +Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) { + // Transition end handler removes its listener. + function handleTransitionEnd( evt ) { + // Make sure this event handler has not bubbled up from a child. + if ( evt.target === evt.currentTarget ) { + $( evt.target ).off( TRANSITIONEND, handleTransitionEnd ); + callback(); + } + } + + callfront(); + + // Transitions are not set until shuffle has loaded to avoid the initial transition. + if ( !this.initialized ) { + $item.css( styles ); + callback(); + return; + } + + // Use CSS Transforms if we have them + if ( this.supported ) { + $item.css( styles ); + $item.on( TRANSITIONEND, handleTransitionEnd ); + + // Use jQuery to animate left/top + } else { + // Save the deferred object which jQuery returns. + var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + // Push the animation to the list of pending animations. + this._animations.push( anim.promise() ); + } +}; + + +/** + * Execute the styles gathered in the style queue. This applies styles to elements, + * triggering transitions. + * @param {boolean} noLayout Whether to trigger a layout event. + * @private + */ +Shuffle.prototype._processStyleQueue = function( noLayout ) { + var $transitions = $(); + + // Iterate over the queue and keep track of ones that use transitions. + each(this.styleQueue, function( transitionObj ) { + if ( transitionObj.skipTransition ) { + this._styleImmediately( transitionObj ); + } else { + $transitions = $transitions.add( transitionObj.$item ); + this._transition( transitionObj ); + } + }, this); + + + if ( $transitions.length > 0 && this.initialized ) { + // Set flag that shuffle is currently in motion. + this.isTransitioning = true; + + if ( this.supported ) { + this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished ); + + // The _transition function appends a promise to the animations array. + // When they're all complete, do things. + } else { + this._whenAnimationsDone( this._movementFinished ); + } + + // A call to layout happened, but none of the newly filtered items will + // change position. Asynchronously fire the callback here. + } else if ( !noLayout ) { + defer( this._layoutEnd, this ); + } + + // Remove everything in the style queue + this.styleQueue.length = 0; +}; + + +/** + * Apply styles without a transition. + * @param {Object} opts Transitions options object. + * @private + */ +Shuffle.prototype._styleImmediately = function( opts ) { + Shuffle._skipTransition(opts.$item[0], function() { + opts.$item.css( this._getStylesForTransition( opts ) ); + }, this); +}; + +Shuffle.prototype._movementFinished = function() { + this.isTransitioning = false; + this._layoutEnd(); +}; + +Shuffle.prototype._layoutEnd = function() { + this._fire( Shuffle.EventType.LAYOUT ); +}; + +Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { + // Add classes and set initial positions. + this._initItems( $newItems ); + + // Add transition to each item. + this._setTransitions( $newItems ); + + // Update the list of + this.$items = this._getItems(); + + // Shrink all items (without transitions). + this._shrink( $newItems ); + each(this.styleQueue, function( transitionObj ) { + transitionObj.skipTransition = true; + }); + + // Apply shrink positions, but do not cause a layout event. + this._processStyleQueue( true ); + + if ( addToEnd ) { + this._addItemsToEnd( $newItems, isSequential ); + } else { + this.shuffle( this.lastFilter ); + } +}; + + +Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) { + // Get ones that passed the current filter + var $passed = this._filter( null, $newItems ); + var passed = $passed.get(); + + // How many filtered elements? + this._updateItemCount(); + + this._layout( passed, true ); + + if ( isSequential && this.supported ) { + this._setSequentialDelay( passed ); + } + + this._revealAppended( passed ); +}; + + +/** + * Triggers appended elements to fade in. + * @param {ArrayLike.} $newFilteredItems Collection of elements. + * @private + */ +Shuffle.prototype._revealAppended = function( newFilteredItems ) { + defer(function() { + each(newFilteredItems, function( el ) { + var $item = $( el ); + this._transition({ + $item: $item, + opacity: 1, + point: $item.data('point'), + scale: DEFAULT_SCALE + }); + }, this); + + this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() { + $(newFilteredItems).css( TRANSITION_DELAY, '0ms' ); + this._movementFinished(); + }); + }, this, this.revealAppendedDelay); +}; + + +/** + * Execute a function when an event has been triggered for every item in a collection. + * @param {jQuery} $collection Collection of elements. + * @param {string} eventName Event to listen for. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callback ) { + var done = 0; + var items = $collection.length; + var self = this; + + function handleEventName( evt ) { + if ( evt.target === evt.currentTarget ) { + $( evt.target ).off( eventName, handleEventName ); + done++; + + // Execute callback if all items have emitted the correct event. + if ( done === items ) { + callback.call( self ); + } + } + } + + // Bind the event to all items. + $collection.on( eventName, handleEventName ); +}; + + +/** + * Execute a callback after jQuery `animate` for a collection has finished. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenAnimationsDone = function( callback ) { + $.when.apply( null, this._animations ).always( $.proxy( function() { + this._animations.length = 0; + callback.call( this ); + }, this )); +}; + + +/** + * Public Methods + */ + +/** + * The magic. This is what makes the plugin 'shuffle' + * @param {string|Function} [category] Category to filter by. Can be a function + * @param {Object} [sortObj] A sort object which can sort the filtered set + */ +Shuffle.prototype.shuffle = function( category, sortObj ) { + if ( !this.enabled || this.isTransitioning ) { + return; + } + + if ( !category ) { + category = ALL_ITEMS; + } + + this._filter( category ); + + // How many filtered elements? + this._updateItemCount(); + + // Shrink each concealed item + this._shrink(); + + // Update transforms on .filtered elements so they will animate to their new positions + this.sort( sortObj ); +}; + + +/** + * Gets the .filtered elements, sorts them, and passes them to layout. + * @param {Object} opts the options object for the sorted plugin + */ +Shuffle.prototype.sort = function( opts ) { + if ( this.enabled && !this.isTransitioning ) { + this._resetCols(); + + var sortOptions = opts || this.lastSort; + var items = this._getFilteredItems().sorted( sortOptions ); + + this._layout( items ); + + this.lastSort = sortOptions; + } +}; + + +/** + * Reposition everything. + * @param {boolean} isOnlyLayout If true, column and gutter widths won't be + * recalculated. + */ +Shuffle.prototype.update = function( isOnlyLayout ) { + if ( this.enabled && !this.isTransitioning ) { + + if ( !isOnlyLayout ) { + // Get updated colCount + this._setColumns(); + } + + // Layout items + this.sort(); + } +}; + + +/** + * Use this instead of `update()` if you don't need the columns and gutters updated + * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations + * could be off. + */ +Shuffle.prototype.layout = function() { + this.update( true ); +}; + + +/** + * New items have been appended to shuffle. Fade them in sequentially + * @param {jQuery} $newItems jQuery collection of new items + * @param {boolean} [addToEnd=false] If true, new items will be added to the end / bottom + * of the items. If not true, items will be mixed in with the current sort order. + * @param {boolean} [isSequential=true] If false, new items won't sequentially fade in + */ +Shuffle.prototype.appended = function( $newItems, addToEnd, isSequential ) { + this._addItems( $newItems, addToEnd === true, isSequential !== false ); +}; + + +/** + * Disables shuffle from updating dimensions and layout on resize + */ +Shuffle.prototype.disable = function() { + this.enabled = false; +}; + + +/** + * Enables shuffle again + * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters + */ +Shuffle.prototype.enable = function( isUpdateLayout ) { + this.enabled = true; + if ( isUpdateLayout !== false ) { + this.update(); + } +}; + + +/** + * Remove 1 or more shuffle items + * @param {jQuery} $collection A jQuery object containing one or more element in shuffle + * @return {Shuffle} The shuffle object + */ +Shuffle.prototype.remove = function( $collection ) { + + // If this isn't a jquery object, exit + if ( !$collection.length || !$collection.jquery ) { + return; + } + + function handleRemoved() { + // Remove the collection in the callback + $collection.remove(); + + // Update things now that elements have been removed. + this.$items = this._getItems(); + this._updateItemCount(); + + this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] ); + + // Let it get garbage collected + $collection = null; + } + + // Hide collection first. + this._toggleFilterClasses( $(), $collection ); + this._shrink( $collection ); + + this.sort(); + + this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) ); +}; + + +/** + * Destroys shuffle, removes events, styles, and classes + */ +Shuffle.prototype.destroy = function() { + // If there is more than one shuffle instance on the page, + // removing the resize handler from the window would remove them + // all. This is why a unique value is needed. + $window.off('.' + this.unique); + + // Reset container styles + this.$el + .removeClass( SHUFFLE ) + .removeAttr('style') + .removeData( SHUFFLE ); + + // Reset individual item styles + this.$items + .removeAttr('style') + .removeData('point') + .removeData('scale') + .removeClass([ + Shuffle.ClassName.CONCEALED, + Shuffle.ClassName.FILTERED, + Shuffle.ClassName.SHUFFLE_ITEM + ].join(' ')); + + // Null DOM references + this.$items = null; + this.$el = null; + this.sizer = null; + this.element = null; + + // Set a flag so if a debounced resize has been triggered, + // it can first check if it is actually destroyed and not doing anything + this.destroyed = true; +}; + + +// Plugin definition +$.fn.shuffle = function( opts ) { + var args = Array.prototype.slice.call( arguments, 1 ); + return this.each(function() { + var $this = $( this ); + var shuffle = $this.data( SHUFFLE ); + + // If we don't have a stored shuffle, make a new one and save it + if ( !shuffle ) { + shuffle = new Shuffle( this, opts ); + $this.data( SHUFFLE, shuffle ); + } else if ( typeof opts === 'string' && shuffle[ opts ] ) { + shuffle[ opts ].apply( shuffle, args ); + } + }); +}; + + +// http://stackoverflow.com/a/962890/373422 +function randomize( array ) { + var tmp, current; + var top = array.length; + + if ( !top ) { + return array; + } + + while ( --top ) { + current = Math.floor( Math.random() * (top + 1) ); + tmp = array[ current ]; + array[ current ] = array[ top ]; + array[ top ] = tmp; + } + + return array; +} + + +// You can return `undefined` from the `by` function to revert to DOM order +// This plugin does NOT return a jQuery object. It returns a plain array because +// jQuery sorts everything in DOM order. +$.fn.sorted = function(options) { + var opts = $.extend({}, $.fn.sorted.defaults, options); + var arr = this.get(); + var revert = false; + + if ( !arr.length ) { + return []; + } + + if ( opts.randomize ) { + return randomize( arr ); + } + + // Sort the elements by the opts.by function. + // If we don't have opts.by, default to DOM order + if ( $.isFunction( opts.by ) ) { + arr.sort(function(a, b) { + + // Exit early if we already know we want to revert + if ( revert ) { + return 0; + } + + var valA = opts.by($(a)); + var valB = opts.by($(b)); + + // If both values are undefined, use the DOM order + if ( valA === undefined && valB === undefined ) { + revert = true; + return 0; + } + + if ( valA < valB || valA === 'sortFirst' || valB === 'sortLast' ) { + return -1; + } + + if ( valA > valB || valA === 'sortLast' || valB === 'sortFirst' ) { + return 1; + } + + return 0; + }); + } + + // Revert to the original array if necessary + if ( revert ) { + return this.get(); + } + + if ( opts.reverse ) { + arr.reverse(); + } + + return arr; +}; + + +$.fn.sorted.defaults = { + reverse: false, // Use array.reverse() to reverse the results + by: null, // Sorting function + randomize: false // If true, this will skip the sorting and return a randomized order in the array +}; + +return Shuffle; + +}); \ No newline at end of file diff --git a/js/mydoc_scroll.html b/js/mydoc_scroll.html new file mode 100644 index 0000000..39fa55e --- /dev/null +++ b/js/mydoc_scroll.html @@ -0,0 +1,240 @@ +--- +title: Scroll layout +type: scroll +keywords: json, scrolling, scrollto, jquery plugin +tags: special_layouts +last_updated: November 30, 2015 +summary: "This page demonstrates how you the integration of a script called ScrollTo, which is used here to link definitions of a JSON code sample to a list of definitions for that particular term. The scenario here is that the JSON blocks are really long, with extensive nesting and subnesting, which makes it difficult for tables below the JSON to adequately explain the term in a usable way." +--- + +{% if site.output == "pdf" %} +{{site.data.alerts.note}} The content on this page doesn't display well on PDF, but I included it anyway so you could see the problems this layout poses if you're including it in PDF. {{site.data.alerts.end}} +{% endif %} + +{% if site.output == "web" %} + + + + + + + + + + +
    +
    + + +
    +
    + + +{{site.data.alerts.note}} This was mostly an experiment to see if there was a better way to document a long JSON code example. I haven't actually used this approach in my own documentation.{{site.data.alerts.end}} + +{% endif %} + diff --git a/pages/mydoc/mydoc_scroll.html b/pages/mydoc/mydoc_scroll.html new file mode 100644 index 0000000..02a259d --- /dev/null +++ b/pages/mydoc/mydoc_scroll.html @@ -0,0 +1,242 @@ +--- +title: Scroll layout +keywords: json, scrolling, scrollto, jquery plugin +tags: [special_layouts] +last_updated: November 30, 2015 +summary: "This page demonstrates how you the integration of a script called ScrollTo, which is used here to link definitions of a JSON code sample to a list of definitions for that particular term. The scenario here is that the JSON blocks are really long, with extensive nesting and subnesting, which makes it difficult for tables below the JSON to adequately explain the term in a usable way." +permalink: mydoc_scroll.html +sidebar: mydoc_sidebar +folder: mydoc +--- + +{% if site.output == "pdf" %} +{{site.data.alerts.note}} The content on this page doesn't display well on PDF, but I included it anyway so you could see the problems this layout poses if you're including it in PDF. {{site.data.alerts.end}} +{% endif %} + +{% if site.output == "web" %} + + + + + + + + + + +
    +
    + + +
    +
    + + +{{site.data.alerts.note}} This was mostly an experiment to see if there was a better way to document a long JSON code example. I haven't actually used this approach in my own documentation.{{site.data.alerts.end}} + +{% endif %} + diff --git a/pages/mydoc/mydoc_shuffle.html b/pages/mydoc/mydoc_shuffle.html new file mode 100644 index 0000000..081b9d8 --- /dev/null +++ b/pages/mydoc/mydoc_shuffle.html @@ -0,0 +1,167 @@ +--- +title: Shuffle layout +tags: [special_layouts] +last_updated: November 30, 2015 +keywords: shuffle, card layout, dynamic grid, doc portal, support portal +summary: "This layout shows an example of a knowledge-base style navigation system, where there is no hierarchy, just groups of pages that have certain tags." +permalink: mydoc_shuffle.html +sidebar: mydoc_sidebar +folder: mydoc +--- + + + +{% if site.output == "pdf" %} +{{site.data.alerts.note}} The content on this page doesn't display well on PDF, but I included it anyway so you could see the problems this layout poses if you're including it in PDF. {{site.data.alerts.end}} +{% endif %} + +{% unless site.output == "pdf" %} + + +{% endunless %} + +
    + + + + + + + +
    + +
    + + +
    + +
    +
    Getting started
    +
    + If you're getting started with Jekyll, see the links in this section. It will take you from the beginning level to comfortable. +
      + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == "getting_started" %} +
    • {{page.title}}
    • + {% endif %} + {% endfor %} + {% endfor %} +
    +
    +
    + +
    + + +
    + +
    +
    Content types
    +
    + This section lists different content types and how to work with them. +
      + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == "content-types" %} +
    • {{page.title}}
    • + {% endif %} + {% endfor %} + {% endfor %} +
    +
    +
    + +
    + + + +
    + +
    +
    Formatting
    +
    + These topics get into formatting syntax, such as images and tables, that you'll use on each of your pages: +
      + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == "formatting" %} +
    • {{page.title}}
    • + {% endif %} + {% endfor %} + {% endfor %} +
    +
    +
    + +
    + +
    + +
    +
    Single Sourcing
    +
    These topics cover strategies for single_sourcing. Single sourcing refers to strategies for re-using the same source in different outputs for different audiences or purposes. +
      + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == "single_sourcing" %} +
    • {{page.title}}
    • + {% endif %} + {% endfor %} + {% endfor %} +
    +
    +
    + +
    + +
    + +
    +
    Publishing
    +
    When you're building, publishing, and deploying your Jekyll site, you might find these topics helpful. +
      + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == "publishing" %} +
    • {{page.title}}
    • + {% endif %} + {% endfor %} + {% endfor %} +
    +
    +
    + +
    + +
    + +
    +
    Special Layouts
    +
    + These pages highlight special layouts outside of the conventional page and TOC hierarchy. +
      + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == "special_layouts" %} +
    • {{page.title}}
    • + {% endif %} + {% endfor %} + {% endfor %} +
    +
    +
    + +
    + +
    + + +
    + +{% unless site.output == "pdf" %} +{% include initialize_shuffle.html %} +{% endunless %} + +{{site.data.alerts.note}} This was mostly an experiment to see if I could break away from the hierarchical TOC and provide a different way of arranging the content. However, this layout is somewhat problematic because it doesn't allow you to browse other navigation options on the side while viewing a topic.{{site.data.alerts.end}} +