From 174ea2cc066b80e710b416958df33bcc969390ea Mon Sep 17 00:00:00 2001 From: jlukic Date: Wed, 23 Apr 2014 18:32:35 -0400 Subject: [PATCH] Updates api, state, visibility, visit to latest iteration --- src/definitions/behaviors/api.js | 108 ++++++++---- src/definitions/behaviors/state.js | 4 +- src/definitions/behaviors/visibility.js | 223 +++++++++++++++++++----- src/definitions/behaviors/visit.js | 64 ++++--- 4 files changed, 295 insertions(+), 104 deletions(-) diff --git a/src/definitions/behaviors/api.js b/src/definitions/behaviors/api.js index 6e978b7d3..e01715aed 100755 --- a/src/definitions/behaviors/api.js +++ b/src/definitions/behaviors/api.js @@ -112,7 +112,6 @@ $.api = $.fn.api = function(parameters) { module.debug('Request cancelled previous request is still pending'); return; } - // pass element metadata to url (value, text) if(settings.defaultData) { $.extend(true, settings.urlData, module.get.defaultData()); @@ -201,39 +200,71 @@ $.api = $.fn.api = function(parameters) { add: { urlData: function(url, urlData) { var - urlVariables + requiredVariables, + optionalVariables ; if(url) { - urlVariables = url.match(settings.regExp.required); - urlData = urlData || settings.urlData; - - if(urlVariables) { - module.debug('Looking for URL variables', urlVariables); - $.each(urlVariables, function(index, templateValue){ + requiredVariables = url.match(settings.regExp.required); + optionalVariables = url.match(settings.regExp.optional); + urlData = urlData || settings.urlData; + if(requiredVariables) { + module.debug('Looking for required URL variables', requiredVariables); + $.each(requiredVariables, function(index, templatedString) { var - term = templateValue.substr( 2, templateValue.length - 3), - termValue = ($.isPlainObject(urlData) && urlData[term] !== undefined) - ? urlData[term] - : ($module.data(term) !== undefined) - ? $module.data(term) - : ($context.data(term) !== undefined) - ? $context.data(term) - : urlData[term] + // allow legacy {$var} style + variable = (templatedString.indexOf('$') !== -1) + ? templatedString.substr(2, templatedString.length - 3) + : templatedString.substr(1, templatedString.length - 2), + value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) + ? urlData[variable] + : ($module.data(variable) !== undefined) + ? $module.data(variable) + : ($context.data(variable) !== undefined) + ? $context.data(variable) + : urlData[variable] ; - module.verbose('Looking for variable', term); - // remove optional value - if(termValue === false) { - module.debug('Removing variable from URL', urlVariables); - url = url.replace('/' + templateValue, ''); - } - // undefined condition - else if(termValue === undefined || !termValue) { - module.error(error.missingParameter, term, url); + // remove value + if(value === undefined) { + module.error(error.requiredParameter, variable, url); url = false; return false; } else { - url = url.replace(templateValue, termValue); + module.verbose('Found required variable', variable, value); + url = url.replace(templatedString, value); + } + }); + } + if(optionalVariables) { + module.debug('Looking for optional URL variables', requiredVariables); + $.each(optionalVariables, function(index, templatedString) { + var + // allow legacy {/$var} style + variable = (templatedString.indexOf('$') !== -1) + ? templatedString.substr(3, templatedString.length - 4) + : templatedString.substr(2, templatedString.length - 3), + value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) + ? urlData[variable] + : ($module.data(variable) !== undefined) + ? $module.data(variable) + : ($context.data(variable) !== undefined) + ? $context.data(variable) + : urlData[variable] + ; + // optional replacement + if(value !== undefined) { + module.verbose('Optional variable Found', variable, value); + url = url.replace(templatedString, value); + } + else { + module.verbose('Optional variable not found', variable); + // remove preceding slash if set + if(url.indexOf('/' + templatedString) !== -1) { + url = url.replace('/' + templatedString, ''); + } + else { + url = url.replace(templatedString, ''); + } } }); } @@ -683,7 +714,8 @@ $.api.settings = { action : false, regExp : { - required: /\{\$([A-z]+)\}/g + required: /\{\$*[A-z0-9]+\}/g, + optional: /\{\/\$*[A-z0-9]+\}/g, }, // data @@ -717,17 +749,17 @@ $.api.settings = { // errors error : { - beforeSend : 'The before send function has aborted the request', - error : 'There was an error with your request', - exitConditions : 'API Request Aborted. Exit conditions met', - JSONParse : 'JSON could not be parsed during error handling', - missingSerialize : 'Serializing a Form requires toJSON to be included', - missingAction : 'API action used but no url was defined', - missingParameter : 'Missing an essential URL parameter: ', - missingURL : 'No URL specified for api event', - parseError : 'There was an error parsing your request', - statusMessage : 'Server gave an error: ', - timeout : 'Your request timed out' + beforeSend : 'The before send function has aborted the request', + error : 'There was an error with your request', + exitConditions : 'API Request Aborted. Exit conditions met', + JSONParse : 'JSON could not be parsed during error handling', + missingAction : 'API action used but no url was defined', + missingSerialize : 'Serializing a Form requires toJSON to be included', + missingURL : 'No URL specified for api event', + parseError : 'There was an error parsing your request', + requiredParameter : 'Missing a required URL parameter: ', + statusMessage : 'Server gave an error: ', + timeout : 'Your request timed out' }, className: { diff --git a/src/definitions/behaviors/state.js b/src/definitions/behaviors/state.js index 505edcedc..c1bfbd2fc 100755 --- a/src/definitions/behaviors/state.js +++ b/src/definitions/behaviors/state.js @@ -166,13 +166,13 @@ $.fn.state = function(parameters) { $module.addClass(className.disabled); }, - enableState: function(state) { + setState: function(state) { if(module.allows(state)) { $module.addClass( className[state] ); } }, - disableState: function(state) { + removeState: function(state) { if(module.allows(state)) { $module.removeClass( className[state] ); } diff --git a/src/definitions/behaviors/visibility.js b/src/definitions/behaviors/visibility.js index 7c1055500..f73f5ad98 100755 --- a/src/definitions/behaviors/visibility.js +++ b/src/definitions/behaviors/visibility.js @@ -65,7 +65,11 @@ $.fn.visibility = function(parameters) { module.bindEvents(); module.instantiate(); - setTimeout(module.checkVisibility, settings.loadWait); + if(settings.type == 'image') { + module.setup.image(); + } + + requestAnimationFrame(module.checkVisibility); }, instantiate: function() { @@ -88,6 +92,7 @@ $.fn.visibility = function(parameters) { }, bindEvents: function() { + module.verbose('Binding visibility events to scroll and resize'); $window .on('resize', module.event.refresh) .on('scroll', module.event.scroll) @@ -99,25 +104,99 @@ $.fn.visibility = function(parameters) { requestAnimationFrame(module.refresh); }, scroll: function() { - requestAnimationFrame(module.checkVisibility); + module.verbose('Scroll position changed'); + if(settings.throttle) { + clearTimeout(module.timer); + module.timer = setTimeout(module.checkVisibility, 200); + } + else { + requestAnimationFrame(module.checkVisibility); + } + } + }, + + precache: function(images, callback) { + if (!(images instanceof Array)) { + images = [images]; + } + var + imagesLength = images.length, + loadedCounter = 0, + cache = [], + cacheImage = document.createElement('img'), + handleLoad = function() { + loadedCounter++; + if (loadedCounter >= images.length) { + if ($.isFunction(callback)) { + callback(); + } + } + } + ; + while (imagesLength--) { + cacheImage = document.createElement('img'); + cacheImage.onload = handleLoad; + cacheImage.onerror = handleLoad; + cacheImage.src = images[imagesLength]; + cache.push(cacheImage); + } + }, + + setup: { + image: function() { + var + src = $module.data('src') + ; + if(src) { + module.verbose('Lazy loading image', src); + settings.once = true; + // show when top visible + module.topVisible(function() { + module.precache(src, function() { + module.set.image(src); + settings.onTopVisible = false; + }); + }); + } + } + }, + + set: { + image: function(src) { + var + offScreen = (module.cache.screen.bottom + settings.offset < module.cache.element.top) + ; + $module + .attr('src', src) + ; + if($.fn.transition !== undefined || offScreen) { + $module.transition(settings.transition, settings.duration); + } + else { + $module.fadeIn(settings.duration); + } } }, refresh: function() { + module.debug('Refreshing constants (element width/height)'); + module.reset(); module.save.position(); module.checkVisibility(); $.proxy(settings.onRefresh, element)(); }, reset: function() { + module.verbose('Reseting all cached values'); module.cache = { + occurred: {}, screen : {}, element : {} }; }, checkVisibility: function() { - module.verbose('Updating visibility of element', module.cache.element); + module.verbose('Checking visibility of element', module.cache.element); module.save.scroll(); module.save.direction(); module.save.screenCalculations(); @@ -131,24 +210,6 @@ $.fn.visibility = function(parameters) { module.bottomPassed(); }, - passing: function(newCallback) { - var - calculations = module.get.elementCalculations(), - screen = module.get.screenCalculations(), - callback = newCallback || settings.onPassing - ; - if(newCallback) { - module.debug('Adding callback for passing', newCallback); - settings.onPassing = newCallback; - } - if(callback && calculations.passing) { - $.proxy(callback, element)(calculations, screen); - } - else { - return calculations.passing; - } - }, - passed: function(amount, newCallback) { var calculations = module.get.elementCalculations(), @@ -164,26 +225,54 @@ $.fn.visibility = function(parameters) { else if(calculations.passing) { $.each(settings.onPassed, function(amount, callback) { if(calculations.bottomVisible || calculations.pixelsPassed > module.get.pixelsPassed(amount)) { - callback(); + module.execute(callback, amount); + } + else { + module.remove.occurred(callback, amount); } }); } }, + passing: function(newCallback) { + var + calculations = module.get.elementCalculations(), + callback = newCallback || settings.onPassing, + callbackName = 'passing' + ; + if(newCallback) { + module.debug('Adding callback for passing', newCallback); + settings.onPassing = newCallback; + } + if(callback && calculations.passing) { + module.execute(callback, callbackName); + } + else { + module.remove.occurred(callbackName); + } + if(newCallback !== undefined) { + return calculations.passing; + } + }, + + topVisible: function(newCallback) { var calculations = module.get.elementCalculations(), - screen = module.get.screenCalculations(), - callback = newCallback || settings.onTopVisible + callback = newCallback || settings.onTopVisible, + callbackName = 'topVisible' ; if(newCallback) { module.debug('Adding callback for top visible', newCallback); settings.onTopVisible = newCallback; } if(callback && calculations.topVisible) { - $.proxy(callback, element)(calculations, screen); + module.execute(callback, callbackName); } else { + module.remove.occurred(callbackName); + } + if(newCallback === undefined) { return calculations.topVisible; } }, @@ -191,17 +280,20 @@ $.fn.visibility = function(parameters) { bottomVisible: function(newCallback) { var calculations = module.get.elementCalculations(), - screen = module.get.screenCalculations(), - callback = newCallback || settings.onBottomVisible + callback = newCallback || settings.onBottomVisible, + callbackName = 'bottomVisible' ; if(newCallback) { module.debug('Adding callback for bottom visible', newCallback); settings.onBottomVisible = newCallback; } if(callback && calculations.bottomVisible) { - $.proxy(callback, element)(calculations, screen); + module.execute(callback, callbackName); } else { + module.remove.occurred(callbackName); + } + if(newCallback === undefined) { return calculations.bottomVisible; } }, @@ -209,17 +301,20 @@ $.fn.visibility = function(parameters) { topPassed: function(newCallback) { var calculations = module.get.elementCalculations(), - screen = module.get.screenCalculations(), - callback = newCallback || settings.onTopPassed + callback = newCallback || settings.onTopPassed, + callbackName = 'topPassed' ; if(newCallback) { module.debug('Adding callback for top passed', newCallback); settings.onTopPassed = newCallback; } if(callback && calculations.topPassed) { - $.proxy(callback, element)(calculations, screen); + module.execute(callback, callbackName); } else { + module.remove.occurred(callbackName); + } + if(newCallback === undefined) { return calculations.topPassed; } }, @@ -227,22 +322,56 @@ $.fn.visibility = function(parameters) { bottomPassed: function(newCallback) { var calculations = module.get.elementCalculations(), - screen = module.get.screenCalculations(), - callback = newCallback || settings.onBottomPassed + callback = newCallback || settings.onBottomPassed, + callbackName = 'bottomPassed' ; if(newCallback) { module.debug('Adding callback for bottom passed', newCallback); settings.bottomPassed = newCallback; } if(callback && calculations.bottomPassed) { - $.proxy(callback, element)(calculations, screen); + module.execute(callback, callbackName); } else { + module.remove.occurred(callbackName); + } + if(newCallback === undefined) { return calculations.bottomPassed; } }, + execute: function(callback, callbackName) { + var + calculations = module.get.elementCalculations(), + screen = module.get.screenCalculations() + ; + if(settings.once && module.get.occurred(callbackName)) { + // multiple callbacks are ignored when times == 'once' + } + else { + module.debug('Conditions met for callback', callbackName, calculations); + $.proxy(callback, element)(calculations, screen); + } + module.save.occurred(callbackName); + }, + + remove: { + occurred: function(callback) { + if(callback) { + module.cache.occurred[callback] = false; + } + else { + module.cache.occurred = {}; + } + } + }, + save: { + occurred: function(callback) { + if(callback) { + module.cache.occurred[callback] = true; + } + }, scroll: function() { module.cache.scroll = $window.scrollTop() + settings.offset; }, @@ -329,8 +458,8 @@ $.fn.visibility = function(parameters) { } module.save.direction(); $.extend(module.cache.screen, { - top : scroll + settings.offset, - bottom : scroll + settings.offset + module.cache.screen.height + top : scroll - settings.offset, + bottom : scroll - settings.offset + module.cache.screen.height }); return module.cache.screen; }, @@ -356,6 +485,12 @@ $.fn.visibility = function(parameters) { } return parseInt(amount, 10); }, + occurred: function(callback) { + return (module.cache.occurred !== undefined) + ? module.cache.occurred[callback] || false + : false + ; + }, direction: function() { if(module.cache.direction === undefined) { module.save.direction(); @@ -585,12 +720,17 @@ $.fn.visibility.settings = { verbose : false, performance : true, - loadWait : 1000, - - watch : true, offset : 0, includeMargin : false, + // visibility check delay in ms + throttle : false, + + // special visibility type + type : false, + transition : 'fade in', + duration : 500, + // array of callbacks onPassed : {}, @@ -601,10 +741,15 @@ $.fn.visibility.settings = { onTopPassed : false, onBottomPassed : false, + once : false, + // utility callbacks onRefresh : function(){}, onScroll : function(){}, + // not used currently waiting for (DOM Mutations API adoption) + // https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#mutation-observers + watch : true, watchedProperties : [ 'offsetWidth', 'offsetHeight', diff --git a/src/definitions/behaviors/visit.js b/src/definitions/behaviors/visit.js index 7720bd850..5a3d7b1f5 100755 --- a/src/definitions/behaviors/visit.js +++ b/src/definitions/behaviors/visit.js @@ -47,12 +47,12 @@ $.visit = $.fn.visit = function(parameters) { module = { initialize: function() { - if(settings.id) { - module.add.id(settings.id); - } - else if(settings.count) { + if(settings.count) { module.store(settings.key.count, settings.count); } + else if(settings.id) { + module.add.id(settings.id); + } else if(settings.increment && methodInvoked !== 'increment') { module.increment(); } @@ -61,7 +61,7 @@ $.visit = $.fn.visit = function(parameters) { }, instantiate: function() { - module.verbose('Storing instance of visit', module); + module.verbose('Storing instance of visit module', module); instance = module; $module .data(moduleNamespace, module) @@ -80,8 +80,8 @@ $.visit = $.fn.visit = function(parameters) { currentValue = module.get.count(), newValue = +(currentValue) + 1 ; - if(id && module.has.visited(id) ) { - module.debug('Unique content already visited, skipping increment', id); + if(id) { + module.add.id(id); } else { if(newValue > settings.limit && !settings.surpass) { @@ -97,8 +97,8 @@ $.visit = $.fn.visit = function(parameters) { currentValue = module.get.count(), newValue = +(currentValue) - 1 ; - if(id && !module.has.visited(id) ) { - module.debug('Decrement skipped, content has not been visited', id); + if(id) { + module.remove.id(id); } else { module.debug('Removing visit'); @@ -110,14 +110,20 @@ $.visit = $.fn.visit = function(parameters) { count: function() { return +(module.retrieve(settings.key.count)) || 0; }, + idCount: function(ids) { + ids = ids || module.get.ids(); + return ids.length; + }, ids: function(delimitedIDs) { + var + idArray = [] + ; delimitedIDs = delimitedIDs || module.retrieve(settings.key.ids); if(typeof delimitedIDs === 'string') { - return delimitedIDs.split(settings.delimiter); - } - else { - return []; + idArray = delimitedIDs.split(settings.delimiter); } + module.verbose('Found visited ID list', idArray); + return idArray; }, storageOptions: function(data) { var @@ -137,7 +143,6 @@ $.visit = $.fn.visit = function(parameters) { }, has: { - visited: function(id, ids) { var visited = false @@ -152,7 +157,6 @@ $.visit = $.fn.visit = function(parameters) { } return visited; } - }, set: { @@ -178,16 +182,16 @@ $.visit = $.fn.visit = function(parameters) { : currentIDs + settings.delimiter + id ; if( module.has.visited(id) ) { - module.debug('Unique content already visited, not adding visit', id); + module.debug('Unique content already visited, not adding visit', id, currentIDs); } else if(id === undefined) { module.debug('ID is not defined'); } else { module.debug('Adding visit to unique content', id); - module.increment(id); module.store(settings.key.ids, newIDs); } + module.set.count( module.get.idCount() ); }, display: function(selector) { var @@ -200,7 +204,6 @@ $.visit = $.fn.visit = function(parameters) { : $element ; } - module.update.display(); } }, @@ -220,6 +223,22 @@ $.visit = $.fn.visit = function(parameters) { newIDs = newIDs.join(settings.delimiter); module.store(settings.key.ids, newIDs ); } + module.set.count( module.get.idCount() ); + } + }, + + check: { + limit: function(value) { + value = value || module.get.count(); + if(value >= settings.limit) { + module.debug('Pages viewed exceeded limit, firing callback', value, settings.limit); + $.proxy(settings.onLimit, this)(value); + } + else if(settings.limit) { + module.debug('Limit not reached', value, settings.limit); + } + $.proxy(settings.onChange, this)(value); + module.update.display(value); } }, @@ -230,11 +249,6 @@ $.visit = $.fn.visit = function(parameters) { module.debug('Updating displayed view count', $displays); $displays.html(value); } - if(value >= settings.limit) { - module.debug('Pages viewed exceeded limit, firing callback', value, settings.limit); - $.proxy(settings.onLimit, this)(value); - } - $.proxy(settings.onChange, this)(value); } }, @@ -255,7 +269,7 @@ $.visit = $.fn.visit = function(parameters) { return; } if(key == settings.key.count) { - module.update.display(value); + module.check.limit(value); } }, retrieve: function(key, value) { @@ -468,7 +482,7 @@ $.fn.visit.settings = { namespace : 'visit', - increment : true, + increment : false, surpass : false, count : false, limit : false,