diff --git a/README.md b/README.md index 6dc96ff..ed6cc9b 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Feedback, bugs, questions? [email](mailto:contact@kumailht.com) me, I'll respond ####3. Use 'less than' and 'greater than' classes as breakpoints to write responsive CSS ```css -.quote.lt500 {background: blue} -.quote.gt150.lt300 {background: red} +.quote.v-lt500 {background: blue} +.quote.v-gt150.v-lt300 {background: red} ``` ####4. Optionally declare start, end and interval values on your `data-respond` attribute to control breakpoint generation diff --git a/responsive-elements.js b/responsive-elements.js index 7ea7c12..797977d 100644 --- a/responsive-elements.js +++ b/responsive-elements.js @@ -22,141 +22,138 @@ // var ResponsiveElements = { - elementsAttributeName: 'data-respond', - maxRefreshRate: 5, - defaults: { - // How soon should you start adding breakpoints - start: 100, - // When to stop adding breakpoints - end: 900, - // At what interval should breakpoints be added? - interval: 50 - }, - init: function() { - var self = this; - $(function() { - self.el = { - window: $(window), - responsive_elements: $('[' + self.elementsAttributeName + ']') - }; - - self.events(); - }); - }, - - addElement: function(element) { - this.el.responsive_elements = this.el.responsive_elements.add(element); - }, - - removeElement: function(element) { - this.el.responsive_elements = this.el.responsive_elements.not(element); - }, - - parseOptions: function(options_string) { - // data-respond="start: 100px; end: 900px; interval: 50px; watch: true;" - if (!options_string) return false; - - this._options_cache = this._options_cache || {}; - if (this._options_cache[options_string]) return this._options_cache[options_string]; - - var options_array = options_string.replace(/\s+/g, '').split(';'), - options_object = {}; - - for (var i = 0; i < options_array.length; i++) { - if (!options_array[i]) continue; - var property_array = (options_array[i]).split(':'); - - var key = property_array[0]; - var value = property_array[1]; - - if (value.slice(-2) === 'px') { - value = value.replace('px', ''); - } - if (!isNaN(value)) { - value = parseInt(value, 10); - } - options_object[key] = value; - } - - this._options_cache[options_string] = options_object; - return options_object; - }, - generateBreakpointsOnAllElements: function() { - var self = ResponsiveElements; - self.el.responsive_elements.each(function(i, _el) { - self.generateBreakpointsOnElement($(_el)); - }); - }, - generateBreakpointsOnElement: function(_el) { - var options_string = _el.attr(this.elementsAttributeName), - options = this.parseOptions(options_string) || this.defaults, - breakpoints = this.generateBreakpoints(_el.width(), options); - - this.cleanUpBreakpoints(_el); - _el.addClass(breakpoints.join(' ')); - }, - generateBreakpoints: function(width, options) { - var start = options.start, - end = options.end, - interval = options.interval, - i = interval > start ? interval : ~~(start / interval) * interval, - classes = []; - - while (i <= end) { - if (i < width) classes.push('gt' + i); - if (i > width) classes.push('lt' + i); - if (i == width) classes.push('lt' + i); - - i += interval; - } - - return classes; - }, - parseBreakpointClasses: function(breakpoints_string) { - var classes = breakpoints_string.split(/\s+/), - breakpointClasses = []; - - $(classes).each(function(i, className) { - if (className.match(/^gt\d+|lt\d+$/)) breakpointClasses.push(className); - }); - - return breakpointClasses; - }, - cleanUpBreakpoints: function(_el) { - var classesToCleanup = this.parseBreakpointClasses(_el.attr('class') || ''); - _el.removeClass(classesToCleanup.join(' ')); - }, - events: function() { - this.generateBreakpointsOnAllElements(); - - this.el.window.bind('resize', this.utils.debounce( - this.generateBreakpointsOnAllElements, this.maxRefreshRate)); - }, - utils: { - // Debounce is part of Underscore.js 1.5.2 http://underscorejs.org - // (c) 2009-2013 Jeremy Ashkenas. Distributed under the MIT license. - debounce: function(func, wait, immediate) { - // Returns a function, that, as long as it continues to be invoked, - // will not be triggered. The function will be called after it stops - // being called for N milliseconds. If `immediate` is passed, - // trigger the function on the leading edge, instead of the trailing. - var result; - var timeout = null; - return function() { - var context = this, - args = arguments; - var later = function() { - timeout = null; - if (!immediate) result = func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) result = func.apply(context, args); - return result; - }; - } - } + elementsAttributeName: 'data-respond', + maxRefreshRate: 5, + defaults: { + // Minimum width to add a breakpoint + min: 100, + // Maximum width to add a breakpoint + max: 900, + // Step amount to skip breakpoints + step: 50 + }, + init: function() { + var self = this; + $(function() { + self.el = { + window: $(window), + responsiveElements: $('[' + self.elementsAttributeName + ']') + }; + + self.events(); + }); + }, + + addElement: function(element) { + this.el.responsiveElements = this.el.responsiveElements.add(element); + }, + + removeElement: function(element) { + this.el.responsiveElements = this.el.responsiveElements.not(element); + }, + + parseOptions: function(optionsString) { + // data-respond="{"min": 100, "max": 900, "step": 50, "watch": true}" + if (!optionsString) return false; + + this._optionsCache = this._optionsCache || {}; + if (this._optionsCache[optionsString]) return this._optionsCache[optionsString]; + + var optionsObject = JSON.parse(optionsString); + + for (var key in optionsObject) { + var value = optionsObject[key]; + + if (value.toString().slice(-2) === 'px') { + value = value.replace('px', ''); + } + + if (!isNaN(value)) { + value = parseInt(value, 10); + } + + optionsObject[key] = value; + } + + this._optionsCache[optionsString] = optionsObject; + + return optionsObject; + }, + generateBreakpointsOnAllElements: function() { + var self = ResponsiveElements; + self.el.responsiveElements.each(function(i, _el) { + self.generateBreakpointsOnElement($(_el)); + }); + }, + generateBreakpointsOnElement: function(_el) { + var optionsString = _el.attr(this.elementsAttributeName), + options = this.parseOptions(optionsString) || this.defaults, + breakpoints = this.generateBreakpoints(_el.width(), options); + + this.cleanUpBreakpoints(_el); + _el.addClass(breakpoints.join(' ')); + }, + generateBreakpoints: function(width, options) { + var min = options.min, + max = options.max, + step = options.step, + i = step > min ? step : ~~(min / step) * step, + classes = []; + + while (i <= max) { + if (i < width) classes.push('v-gt' + i); + if (i >= width) classes.push('v-lt' + i); + + i += step; + } + + return classes; + }, + parseBreakpointClasses: function(breakpointsString) { + var classes = breakpointsString.split(/\s+/), + breakpointClasses = []; + + $(classes).each(function(i, className) { + if (className.match(/^v-gt\d+|v-lt\d+$/)) breakpointClasses.push(className); + }); + + return breakpointClasses; + }, + cleanUpBreakpoints: function(_el) { + var classesToCleanup = this.parseBreakpointClasses(_el.attr('class') || ''); + _el.removeClass(classesToCleanup.join(' ')); + }, + events: function() { + this.generateBreakpointsOnAllElements(); + + this.el.window.bind('resize', this.utils.debounce( + this.generateBreakpointsOnAllElements, this.maxRefreshRate)); + }, + utils: { + // Debounce is part of Underscore.js 1.5.2 http://underscorejs.org + // (c) 2009-2013 Jeremy Ashkenas. Distributed under the MIT license. + debounce: function(func, wait, immediate) { + // Returns a function, that, as long as it continues to be invoked, + // will not be triggered. The function will be called after it stops + // being called for N milliseconds. If `immediate` is passed, + // trigger the function on the leading edge, instead of the trailing. + var result; + var timeout = null; + return function() { + var context = this, + args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + } + } }; ResponsiveElements.init(); diff --git a/tests/tests.js b/tests/tests.js index f57829a..a7a52f1 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -1,45 +1,47 @@ +var assert = buster.assert || assert; + buster.testCase("Test breakpoints generation", { testBreakpointGeneration: function() { var breakpoints = ResponsiveElements.generateBreakpoints(420, { - start: 300, - end: 500, - interval: 50 + min: 300, + max: 500, + step: 50 }); - var expected = ["gt300", "gt350", "gt400", "lt450", "lt500"]; + var expected = ["v-gt300", "v-gt350", "v-gt400", "v-lt450", "v-lt500"]; assert.equals(breakpoints, expected); }, outOfBoundBreakpoint: function() { var breakpoints = ResponsiveElements.generateBreakpoints(920, { - start: 300, - end: 500, - interval: 50 + min: 300, + max: 500, + step: 50 }); - var expected = ["gt300", "gt350", "gt400", "gt450", "gt500"]; + var expected = ["v-gt300", "v-gt350", "v-gt400", "v-gt450", "v-gt500"]; assert.equals(breakpoints, expected); }, outOfBoundBreakpoint2: function() { var breakpoints = ResponsiveElements.generateBreakpoints(120, { - start: 300, - end: 500, - interval: 50 + min: 300, + max: 500, + step: 50 }); - var expected = ["lt300", "lt350", "lt400", "lt450", "lt500"]; + var expected = ["v-lt300", "v-lt350", "v-lt400", "v-lt450", "v-lt500"]; assert.equals(breakpoints, expected); }, invalidInterval: function() { var breakpoints = ResponsiveElements.generateBreakpoints(120, { - start: 300, - end: 500, - interval: 1000 + min: 300, + max: 500, + step: 1000 }); var expected = []; assert.equals(breakpoints, expected); }, invalidStart: function() { var breakpoints = ResponsiveElements.generateBreakpoints(200, { - start: 1000, - end: 500, - interval: 50 + min: 1000, + max: 500, + step: 50 }); var expected = []; assert.equals(breakpoints, expected); @@ -49,42 +51,38 @@ buster.testCase("Test breakpoints generation", { buster.testCase("Test breakpoint classes parsing", { testBreakpointClassesParsing: function() { var parsed_classes = ResponsiveElements.parseBreakpointClasses( - 'lt238 gt390 ewjfewqh weuhltwioa qwuigtwio gtweih lthiew 3829'); - var expected = ['lt238', 'gt390']; + 'v-lt238 v-gt390 ewjfewqh weuhltwioa qwuigtwio gtweih lthiew 3829'); + var expected = ['v-lt238', 'v-gt390']; - assert.equals(parsed_classes, expected); - } -}); + assert.equals(parsed_classes, expected); + } + }); -buster.testCase("Test option parsing", { - testOptionsParsing: function() { - var options = ResponsiveElements.parseOptions('start: 100px; end: 900px; interval: 50px;'); - var expected = { - start: 100, - end: 900, - interval: 50 - }; + buster.testCase("Test option parsing", { + testOptionsParsing: function() { + var options = ResponsiveElements.parseOptions('{"min": 100, "max": 900, "step": 50}'); + var expected = { + min: 100, + max: 900, + step: 50 + }; - assert.equals(options, expected); - }, - testOptionsParsingWithSpaces: function() { - var options = ResponsiveElements.parseOptions('st art: 100 px; end : 900px; interval: 50px;'); - var expected = { - start: 100, - end: 900, - interval: 50 - }; + assert.equals(options, expected); + }, + testOptionsParsingWithStringValues: function() { + var options = ResponsiveElements.parseOptions('{"min": "100px", "max": "900px", "step": "50px"}'); + var expected = { + min: 100, + max: 900, + step: 50 + }; - assert.equals(options, expected); - }, - testOptionsParsingWithoutPixels: function() { - var options = ResponsiveElements.parseOptions('start: 100; end: 900; interval: 50'); - var expected = { - start: 100, - end: 900, - interval: 50 - }; - assert.equals(typeof options.start, typeof expected.start); - } -}); + assert.equals(options, expected); + }, + testOptionsParsingWithInvalidJSON: function() { + assert.exception(function() { + ResponsiveElements.parseOptions('{min'); + }, {name: 'SyntaxError'}); + } + }); diff --git a/website/index.html b/website/index.html index 97f66c5..c457609 100644 --- a/website/index.html +++ b/website/index.html @@ -28,21 +28,21 @@ .quote .author-photo, .quote .author-thumb {display:none;} - .quote.gt250 .author-context {font-family:sans-serif; color:#aaa} + .quote.v-gt250 .author-context {font-family:sans-serif; color:#aaa} - .quote.lt150 .words {font-size:14px; word-spacing:-1px; line-height:1.2} - .quote.lt150 .author {font-size:11px; margin-top:5px} + .quote.v-lt150 .words {font-size:14px; word-spacing:-1px; line-height:1.2} + .quote.v-lt150 .author {font-size:11px; margin-top:5px} - .quote.gt250 .words {font-size:22px} - .quote.gt250 .author {margin-top:20px} - .quote.gt250 .author-context {display:block; font-size:10px;} - .quote.gt250 .author-thumb {display:block; width:40px; float:left; margin:20px 10px 0 0;} + .quote.v-gt250 .words {font-size:22px} + .quote.v-gt250 .author {margin-top:20px} + .quote.v-gt250 .author-context {display:block; font-size:10px;} + .quote.v-gt250 .author-thumb {display:block; width:40px; float:left; margin:20px 10px 0 0;} - .quote.gt450 .words {font-size:26px;} - .quote.gt450 .author {font-size:16px} - .quote.gt450 .author-context {display:block; font-size:12px;} - .quote.gt450 .author-thumb {display:none;} - .quote.gt450 .author-photo {display:block; width:170px; float:right; margin-left:20px} + .quote.v-gt450 .words {font-size:26px;} + .quote.v-gt450 .author {font-size:16px} + .quote.v-gt450 .author-context {display:block; font-size:12px;} + .quote.v-gt450 .author-thumb {display:none;} + .quote.v-gt450 .author-photo {display:block; width:170px; float:right; margin-left:20px}