From 595a94f1bdfaa4905ff51d9044e74105c81e6ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Mon, 13 Apr 2015 18:21:16 +0200 Subject: [PATCH 01/17] fix(gridster): ensure widget dimensions and coords are always ints Thanks to @sogawa for reporting --- src/jquery.gridster.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 7bac91186..df211c594 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -858,10 +858,10 @@ ) { $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); $el.attr({ - 'data-col': wgd.col, - 'data-row': wgd.row, - 'data-sizex': wgd.size_x, - 'data-sizey': wgd.size_y + 'data-col': parseInt(wgd.col, 10), + 'data-row': parseInt(wgd.row, 10), + 'data-sizex': parseInt(wgd.size_x, 10), + 'data-sizey': parseInt(wgd.size_y, 10) }); posChanged = true; } From b16293268c6aa4be2ba0c8fb1b9806e590227606 Mon Sep 17 00:00:00 2001 From: jcp Date: Mon, 1 Dec 2014 21:12:42 +0100 Subject: [PATCH 02/17] fix(gridster): destroy resize_api Call `resize_api.destroy()` to fix memory leak. Closes #473 --- src/jquery.gridster.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index df211c594..de7f06d60 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -3118,6 +3118,10 @@ this.drag_api.destroy(); } + if (this.resize_api) { + this.resize_api.destroy(); + } + this.remove_style_tags(); remove && this.$el.remove(); From ccbac1fcc3b88e4ad7c29ffaf22bd8be801e9e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Tue, 14 Apr 2015 11:28:05 +0200 Subject: [PATCH 03/17] chore: bump up jquery version to 2.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23354b5a3..cc546b29d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ ], "keywords": [], "dependencies": { - "jquery": "git+https://github.com/jquery/jquery.git#2.0.3" + "jquery": "2.1.3" }, "devDependencies": { "grunt": "~0.4.1", From 446852a260aab2e7caf772a62fbde8b518c38816 Mon Sep 17 00:00:00 2001 From: joaoafrmartins Date: Wed, 17 Sep 2014 00:03:02 +0100 Subject: [PATCH 04/17] feat(gridster): Common.js support Closes #434 --- src/jquery.collision.js | 5 +++-- src/jquery.coords.js | 7 ++++--- src/jquery.draggable.js | 5 +++-- src/jquery.gridster.extras.js | 5 +++-- src/jquery.gridster.js | 7 +++++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/jquery.collision.js b/src/jquery.collision.js index 4a15e8e34..a81d41406 100644 --- a/src/jquery.collision.js +++ b/src/jquery.collision.js @@ -7,8 +7,9 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.coords.js')); + } else if (typeof define === 'function' && define.amd) { define('gridster-collision', ['jquery', 'gridster-coords'], factory); } else { root.GridsterCollision = factory(root.$ || root.jQuery, diff --git a/src/jquery.coords.js b/src/jquery.coords.js index 821b67c8b..ff7c0d68a 100644 --- a/src/jquery.coords.js +++ b/src/jquery.coords.js @@ -7,11 +7,12 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + if (typeof exports === 'object') { + module.exports = factory(require('jquery')); + } else if (typeof define === 'function' && define.amd) { define('gridster-coords', ['jquery'], factory); } else { - root.GridsterCoords = factory(root.$ || root.jQuery); + root.GridsterCoords = factory(root.$ || root.jQuery); } }(this, function($) { diff --git a/src/jquery.draggable.js b/src/jquery.draggable.js index d9b5d9884..2822b3b17 100644 --- a/src/jquery.draggable.js +++ b/src/jquery.draggable.js @@ -7,8 +7,9 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + if (typeof exports === 'object') { + module.exports = factory(require('jquery')); + } else if (typeof define === 'function' && define.amd) { define('gridster-draggable', ['jquery'], factory); } else { root.GridsterDraggable = factory(root.$ || root.jQuery); diff --git a/src/jquery.gridster.extras.js b/src/jquery.gridster.extras.js index e7ea59f8b..6915a848b 100644 --- a/src/jquery.gridster.extras.js +++ b/src/jquery.gridster.extras.js @@ -1,6 +1,7 @@ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.gridster.js')); + } else if (typeof define === 'function' && define.amd) { define(['jquery', 'gridster'], factory); } else { root.Gridster = factory(root.$ || root.jQuery, root.Gridster); diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index de7f06d60..c9d5ea82d 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -7,8 +7,11 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + if (typeof exports === 'object') { + module.exports = factory(require('jquery'), + require('./jquery.draggable.js'), require('./jquery.collision.js'), + require('./utils.js')); + } else if (typeof define === 'function' && define.amd) { define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); } else { root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, From 43148b87e523352a7f9d01479c6fed3e87f46ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Tue, 14 Apr 2015 11:28:27 +0200 Subject: [PATCH 05/17] feat(gridster): browserify compatibility --- package.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package.json b/package.json index cc546b29d..1ec7f199e 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,13 @@ "qunitjs": "~1.11.0", "grunt-bump": "0.0.11", "grunt-conventional-changelog": "~1.0.0" + }, + "browserify": { + "transform": [ + "browserify-shim" + ] + }, + "browserify-shim": { + "jquery": "global:$" } } From d3f25f3fbbcc738d8b3702d122533e64f37acd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Mon, 13 Apr 2015 18:02:19 +0200 Subject: [PATCH 06/17] feat(draggable): autoscrolling If the element being dragged is inside a scrollable container, keeping the mouse in one of the sides of the container will trigger scroll. --- src/jquery.draggable.js | 286 +++++++++++++++++++++++++++++----------- 1 file changed, 209 insertions(+), 77 deletions(-) diff --git a/src/jquery.draggable.js b/src/jquery.draggable.js index 2822b3b17..d26369384 100644 --- a/src/jquery.draggable.js +++ b/src/jquery.draggable.js @@ -23,9 +23,13 @@ limit: true, offset_left: 0, autoscroll: true, + scroll_ms: 10, + scroll_px: 4, + scroll_trigger_width: 40, + scroller: null, ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'], // or function handle: null, - container_width: 0, // 0 == auto + container_width: null, // null == auto move_element: true, helper: false, // or 'clone' remove_helper: true @@ -35,17 +39,46 @@ }; var $window = $(window); - var dir_map = { x : 'left', y : 'top' }; var isTouch = !!('ontouchstart' in window); - var capitalize = function(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - }; + var uniqId = (function() { + var idCounter = 0; + return function() { + return ++idCounter + ''; + }; + })(); - var idCounter = 0; - var uniqId = function() { - return ++idCounter + ''; - } + var scrollParent = (function() { + function parents(node, ps) { + if (node.parentNode === null) { return ps; } + return parents(node.parentNode, ps.concat([node])); + } + + function overflow(node) { + var style = getComputedStyle(node, null); + return (style.getPropertyValue('overflow') + + style.getPropertyValue('overflow-y') + + style.getPropertyValue('overflow-x')); + } + + function scroll(node) { + return (/(auto|scroll)/).test(overflow(node)); + } + + return function(node) { + if (! (node instanceof HTMLElement)) { + return; + } + + var ps = parents(node.parentNode, []); + + for (var i = 0; i < ps.length; i += 1) { + if (scroll(ps[i])) { return ps[i]; } + } + + return window; + }; + })(); /** * Basic drag implementation for DOM elements inside a container. @@ -63,11 +96,27 @@ * the mouse must move before dragging should start. * @param {Boolean} [options.limit] Constrains dragging to the width of * the container - * @param {Object|Function} [options.ignore_dragging] Array of node names + * @param {Number} [options.offset_left] Offset added to the item + * @param {Boolean} [options.autoscroll] Autoscroll when dragging if necessary + * @param {Number} [options.scroll_ms] Interval of time in ms during each scroll displacement + * @param {Number} [options.scroll_px] Number of px to move the scroll + * @param {Number} [options.scroll_trigger_width] Width of the hot areas on + * the sides + * @param {False|HTMLElement} [options.scroller] The element that contains the scroll. + * If not specified is calculated automatically. + * @param {Array|Function} [options.ignore_dragging] Array of node names * that sould not trigger dragging, by default is `['INPUT', 'TEXTAREA', * 'SELECT', 'BUTTON']`. If a function is used return true to ignore dragging. - * @param {offset_left} [options.offset_left] Offset added to the item * that is being dragged. + * @param {String} [options.handle] Specify a CSS selector to drag items + * using specific elements as handlers. + * @param {Null|Number} [options.container_width] If specified, force the width + * of the container element. + * @param {Boolean} [options.move_element] Move the HTML element when dragging + * @param {False|'clone'} [options.helper] If set to 'clone', clone the element + * and move the copy instead of the original + * @param {False|'clone'} [options.remove_helper] Remove the helper from DOM + * when the dragging stops. Only when using opts.helper = 'clone'. * @param {Number} [options.drag] Executes a callback when the mouse is * moved during the dragging. * @param {Number} [options.start] Executes a callback when the drag @@ -80,7 +129,6 @@ this.options = $.extend({}, defaults, options); this.$document = $(document); this.$container = $(el); - this.$dragitems = $(this.options.items, this.$container); this.is_dragging = false; this.player_min_left = 0 + this.options.offset_left; this.id = uniqId(); @@ -94,13 +142,10 @@ fn.init = function() { var pos = this.$container.css('position'); - this.calculate_dimensions(); this.$container.css('position', pos === 'static' ? 'relative' : pos); this.disabled = false; this.events(); - - $(window).bind(this.nsEvent('resize'), - throttle($.proxy(this.calculate_dimensions, this), 200)); + this.setup_scroll(); }; fn.nsEvent = function(ev) { @@ -111,7 +156,7 @@ this.pointer_events = { start: this.nsEvent('touchstart') + ' ' + this.nsEvent('mousedown'), move: this.nsEvent('touchmove') + ' ' + this.nsEvent('mousemove'), - end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup'), + end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup') }; this.$container.on(this.nsEvent('selectstart'), @@ -130,6 +175,18 @@ }, this)); }; + fn.setup_scroll = function() { + this.$scroller = $window; + + if (!this.options.autoscroll) { return; } + + this.$scroller = $(this.options.scroller ? + this.options.scroller : scrollParent(this.$container.get(0))); + + $window.bind(this.nsEvent('resize'), + throttle($.proxy(this.on_resize, this), 200)); + }; + fn.get_actual_pos = function($el) { var pos = $el.position(); return pos; @@ -151,15 +208,22 @@ fn.get_offset = function(e) { e.preventDefault(); + var mouse_actual_pos = this.get_mouse_pos(e); var diff_x = Math.round( mouse_actual_pos.left - this.mouse_init_pos.left); var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top); + var scroll_el = this.$scroller.is($window) ? document.body : this.$scroller[0]; + var player_el = this.helper ? this.$helper[0] : this.$player[0]; + var scroller_contains_player = $.contains(scroll_el, player_el); + var scroll_diff_left = this.$scroller.scrollLeft() - this.init_scroll_left; + var scroll_diff_top = this.$scroller.scrollTop() - this.init_scroll_top; + var left = Math.round(this.el_init_offset.left + - diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x); + diff_x - this.baseX + scroll_diff_left); var top = Math.round(this.el_init_offset.top + - diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y); + diff_y - this.baseY + scroll_diff_top); if (this.options.limit) { if (left > this.player_max_left) { @@ -174,11 +238,15 @@ left: left, top: top }, + player: { + left: scroller_contains_player ? left : left - scroll_diff_left, + top: scroller_contains_player ? top : top - scroll_diff_top + }, pointer: { left: mouse_actual_pos.left, top: mouse_actual_pos.top, - diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x), - diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y) + diff_left: diff_x + scroll_diff_left, + diff_top: diff_y + scroll_diff_top } }; }; @@ -204,63 +272,110 @@ }; - fn.scroll_in = function(axis, data) { - var dir_prop = dir_map[axis]; - - var area_size = 50; - var scroll_inc = 30; - - var is_x = axis === 'x'; - var window_size = is_x ? this.window_width : this.window_height; - var doc_size = is_x ? $(document).width() : $(document).height(); - var player_size = is_x ? this.$player.width() : this.$player.height(); - - var next_scroll; - var scroll_offset = $window['scroll' + capitalize(dir_prop)](); - var min_window_pos = scroll_offset; - var max_window_pos = min_window_pos + window_size; - - var mouse_next_zone = max_window_pos - area_size; // down/right - var mouse_prev_zone = min_window_pos + area_size; // up/left - - var abs_mouse_pos = min_window_pos + data.pointer[dir_prop]; - - var max_player_pos = (doc_size - window_size + player_size); - - if (abs_mouse_pos >= mouse_next_zone) { - next_scroll = scroll_offset + scroll_inc; - if (next_scroll < max_player_pos) { - $window['scroll' + capitalize(dir_prop)](next_scroll); - this['scroll_offset_' + axis] += scroll_inc; - } + fn.manage_scroll = function(e) { + if (e.fakeEvent) { + return; + } else { + this.clear_scrolls(); } - if (abs_mouse_pos <= mouse_prev_zone) { - next_scroll = scroll_offset - scroll_inc; - if (next_scroll > 0) { - $window['scroll' + capitalize(dir_prop)](next_scroll); - this['scroll_offset_' + axis] -= scroll_inc; + var mouse = this.get_mouse_pos(e); + var isWindow = this.$scroller[0] === window; + var mx = mouse.left; + var my = mouse.top; + var area_weight = this.options.scroll_trigger_width; + var scroller = this.scroller; + var side_to_scroll = { + left: 'Left', + right: 'Left', + top: 'Top', + bottom: 'Top' + }; + var hot_areas = { + top: { + x: scroller.left, + y: scroller.top, + x1: scroller.left + scroller.width, + y1: scroller.top + area_weight + }, + right: { + x: scroller.left + scroller.width - area_weight, + y: scroller.top, + x1: scroller.left + scroller.width, + y1: scroller.top + scroller.height + }, + bottom: { + x: scroller.left, + y: scroller.top + scroller.height - area_weight, + x1: scroller.left + scroller.width, + y1: scroller.top + scroller.height + }, + left: { + x: scroller.left, + y: scroller.top, + x1: scroller.left + area_weight, + y1: scroller.top + scroller.height } + }; + + if (! isWindow) { + mx += $window.scrollLeft(); + my += $window.scrollTop(); } - return this; + // store scroll intervals to later clear them + this.scroll_intervals || (this.scroll_intervals = {}); + + $.each(hot_areas, function(side, bounds) { + var interval = this.scroll_intervals[side]; + var x_movement = (side === 'left' || side === 'right'); + var y_movement = (side === 'top' || side === 'bottom'); + var is_active = (mx >= bounds.x && mx <= bounds.x1 && + my >= bounds.y && my <= bounds.y1); + + // clean current setIntervals for non hovered areas + if (!is_active) { + interval && clearInterval(interval); + this.scroll_intervals[side] = null; + return; + } + + // if no setIntervals for hovered areas, set them + if (is_active && !interval) { + this.scroll_intervals[side] = setInterval(function() { + var scroll_method = 'scroll' + side_to_scroll[side]; + var dir = (side === 'left' || side === 'top') ? -1 : 1; + var offset = this.options.scroll_px * dir; + + // scroll view + if (isWindow) { + var scroll_by = x_movement ? [offset, 0] : [0, offset]; + this.$scroller[0].scrollBy.apply(this.$scroller[0], scroll_by); + } else { + this.$scroller[0][scroll_method] += offset; + } + + // trigger mousemove event + var mme = $.Event('mousemove'); + mme.clientX = mouse.left + (x_movement ? offset : 0); + mme.clientY = mouse.top + (y_movement ? offset : 0); + mme.fakeEvent = true; + this.$document.trigger(mme); + }.bind(this), this.options.scroll_ms); + } + }.bind(this)); }; - - fn.manage_scroll = function(data) { - this.scroll_in('x', data); - this.scroll_in('y', data); - }; - - - fn.calculate_dimensions = function(e) { - this.window_height = $window.height(); - this.window_width = $window.width(); + fn.clear_scrolls = function() { + if (!this.scroll_intervals) { return; } + $.each(this.scroll_intervals, function(side) { + clearInterval(this.scroll_intervals[side]); + this.scroll_intervals[side] = null; + }.bind(this)); }; fn.drag_handler = function(e) { - var node = e.target.nodeName; // skip if drag is disabled, or click was not done with the mouse primary button if (this.disabled || e.which !== 1 && !isTouch) { return; @@ -316,7 +431,10 @@ var offset = this.$container.offset(); this.baseX = Math.round(offset.left); this.baseY = Math.round(offset.top); - this.initial_container_width = this.options.container_width || this.$container.width(); + + if (this.options.autoscroll) { + this.calculate_scroll_dimensions(); + } if (this.options.helper === 'clone') { this.$helper = this.$player.clone() @@ -326,13 +444,10 @@ this.helper = false; } - this.win_offset_y = $(window).scrollTop(); - this.win_offset_x = $(window).scrollLeft(); - this.scroll_offset_y = 0; - this.scroll_offset_x = 0; + this.init_scroll_top = this.$scroller.scrollTop(); + this.init_scroll_left = this.$scroller.scrollLeft(); this.el_init_offset = this.$player.offset(); this.player_width = this.$player.width(); - this.player_height = this.$player.height(); this.set_limits(this.options.container_width); @@ -346,13 +461,13 @@ fn.on_dragmove = function(e) { var data = this.get_drag_data(e); - this.options.autoscroll && this.manage_scroll(data); + this.options.autoscroll && this.manage_scroll(e); if (this.options.move_element) { (this.helper ? this.$helper : this.$player).css({ 'position': 'absolute', - 'left' : data.position.left, - 'top' : data.position.top + 'left' : data.player.left, + 'top' : data.player.top }); } @@ -372,6 +487,8 @@ var data = this.get_drag_data(e); this.drag_start = false; + this.clear_scrolls(); + if (this.options.stop) { this.options.stop.call(this.$player, e, data); } @@ -393,6 +510,21 @@ return false; }; + + fn.calculate_scroll_dimensions = function() { + this.scroller = this.$scroller.is($window) ? + {top: 0, left: 0} : this.$scroller.offset(); + + this.scroller.width = this.$scroller.width(); + this.scroller.height = this.$scroller.height(); + }; + + fn.on_resize = function(e) { + if (this.options.autoscroll) { + this.calculate_scroll_dimensions(); + } + }; + fn.enable = function() { this.disabled = false; }; @@ -406,7 +538,7 @@ this.$container.off(this.ns); this.$document.off(this.ns); - $(window).off(this.ns); + $window.off(this.ns); $.removeData(this.$container, 'drag'); }; From d947175257d686801154a403016fd2ec7e6d40c2 Mon Sep 17 00:00:00 2001 From: Stijn Verrept Date: Mon, 8 Sep 2014 16:45:46 +0200 Subject: [PATCH 07/17] fix(gridster): wrong addition solved in add_faux_rows/cols by adding parseInt Closes #426, #425 --- src/jquery.gridster.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index c9d5ea82d..566cc3dbf 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -2963,7 +2963,7 @@ */ fn.add_faux_rows = function(rows) { var actual_rows = this.rows; - var max_rows = actual_rows + (rows || 1); + var max_rows = actual_rows + parseInt(rows || 1, 10); for (var r = max_rows; r > actual_rows; r--) { for (var c = this.cols; c >= 1; c--) { @@ -2989,7 +2989,7 @@ */ fn.add_faux_cols = function(cols) { var actual_cols = this.cols; - var max_cols = actual_cols + (cols || 1); + var max_cols = actual_cols + parseInt(cols || 1, 10); max_cols = Math.min(max_cols, this.options.max_cols); for (var c = actual_cols + 1; c <= max_cols; c++) { From c6226306c2ce9aa7d45d774d7de19088acba0c66 Mon Sep 17 00:00:00 2001 From: aegisrunestone Date: Tue, 2 Sep 2014 14:53:58 +0800 Subject: [PATCH 08/17] fix(gridster): changed "instanceof jQuery" to "instanceof $" Original code will break if jquery noconflict is applied, where $ and jQuery produces jQuery instance of different versions. --- src/jquery.gridster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 566cc3dbf..5f76454ad 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -842,7 +842,7 @@ * than the original. */ fn.register_widget = function($el) { - var isDOM = $el instanceof jQuery; + var isDOM = $el instanceof $; var wgd = isDOM ? this.dom_to_coords($el) : $el; var posChanged = false; isDOM || ($el = wgd.el); From 8080daccd185a5d3e32e1ea049ad0134e283fc27 Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Thu, 10 Jul 2014 21:15:52 +0100 Subject: [PATCH 09/17] docs(gridster): fixed a typo in Gridster class documentation --- src/jquery.gridster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 5f76454ad..893a499b3 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -126,7 +126,7 @@ * `[min_cols_occupied, min_rows_occupied]` * @param {Function} [options.resize.start] Function executed * when resizing starts. - * @param {Function} [otions.resize.resize] Function executed + * @param {Function} [options.resize.resize] Function executed * during the resizing. * @param {Function} [options.resize.stop] Function executed * when resizing stops. From 93cd01ecc39805f84895221ef53a2318575abaef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 17:14:24 +0200 Subject: [PATCH 10/17] chore(gridster): remove unused code --- src/jquery.gridster.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 893a499b3..13d43820b 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -139,7 +139,6 @@ this.$wrapper = this.$el.parent(); this.$widgets = this.$el.children( this.options.widget_selector).addClass('gs-w'); - this.widgets = []; this.$changed = $([]); this.wrapper_width = this.$wrapper.width(); this.min_widget_width = (this.options.widget_margins[0] * 2) + @@ -2938,9 +2937,7 @@ width: this.min_widget_width, height: this.min_widget_height, col: col, - row: row, - original_col: col, - original_row: row + row: row }).coords(); if (!$.isArray(this.gridmap[col])) { From 2002c455957016cb441a317dbbb6e5f6662cb35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 17:15:09 +0200 Subject: [PATCH 11/17] feat(gridster.css): remove possible default pading --- src/jquery.gridster.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jquery.gridster.css b/src/jquery.gridster.css index 52feaab18..5faed68d6 100644 --- a/src/jquery.gridster.css +++ b/src/jquery.gridster.css @@ -4,6 +4,7 @@ .gridster > * { margin: 0 auto; + padding: 0; -webkit-transition: height .4s, width .4s; -moz-transition: height .4s, width .4s; -o-transition: height .4s, width .4s; From e258d595b02c07572a9163386d63df931799bfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 17:10:09 +0200 Subject: [PATCH 12/17] tests(gridster): add basic test suite for gridster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests are build with mocha + chai, and you can run them from the command line with `grunt karma` or opening `test/index.html` in your browser. This is a first approach, there are hundreds of tests more that should be added. I’ve ported some tests on PRs but more needs to be done. --- Gruntfile.js | 7 + package.json | 27 +- test/amd/index.html | 40 ++ test/amd/index.js | 71 ++++ test/index.html | 29 ++ test/index.js | 709 ++++++++++++++++++++++++++++++++++++ test/jquery.gridder.html | 67 ---- test/jquery.gridder_test.js | 38 -- test/karma.conf.js | 39 ++ test/lib/test.css | 25 ++ test/lib/test_utils.js | 198 ++++++++++ 11 files changed, 1136 insertions(+), 114 deletions(-) create mode 100644 test/amd/index.html create mode 100644 test/amd/index.js create mode 100644 test/index.html create mode 100644 test/index.js delete mode 100644 test/jquery.gridder.html delete mode 100644 test/jquery.gridder_test.js create mode 100644 test/karma.conf.js create mode 100644 test/lib/test.css create mode 100644 test/lib/test_utils.js diff --git a/Gruntfile.js b/Gruntfile.js index 48be91324..a1176b4d4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -156,6 +156,12 @@ module.exports = function(grunt) { watch: { files: ['libs/*.js', 'src/*.js', 'src/*.css', 'Gruntfile.js'], tasks: ['concat', 'uglify', 'cssmin'] + }, + + karma: { + gridster: { + configFile: 'test/karma.conf.js' + } } }); @@ -168,6 +174,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-yuidoc'); grunt.loadNpmTasks('grunt-bump'); grunt.loadNpmTasks('grunt-conventional-changelog'); + grunt.loadNpmTasks('grunt-karma'); // Default task. grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'cssmin']); diff --git a/package.json b/package.json index 1ec7f199e..5d1d10af6 100644 --- a/package.json +++ b/package.json @@ -26,17 +26,26 @@ "jquery": "2.1.3" }, "devDependencies": { + "chai": "^2.2.0", "grunt": "~0.4.1", - "grunt-contrib-uglify": "~0.2.0", - "grunt-contrib-jshint": "~0.3.0", - "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-watch": "~0.3.1", - "grunt-contrib-cssmin": "~0.5.0", - "grunt-contrib-yuidoc": "~0.4.0", - "bower": "~0.9.2", - "qunitjs": "~1.11.0", "grunt-bump": "0.0.11", - "grunt-conventional-changelog": "~1.0.0" + "grunt-contrib-concat": "~0.1.3", + "grunt-contrib-cssmin": "~0.5.0", + "grunt-contrib-jshint": "~0.3.0", + "grunt-contrib-uglify": "~0.2.0", + "grunt-contrib-watch": "~0.3.1", + "grunt-contrib-yuidoc": "~0.4.0", + "grunt-conventional-changelog": "~1.0.0", + "grunt-karma": "^0.10.1", + "karma": "^0.12.31", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^0.1.7", + "karma-mocha": "^0.1.10", + "karma-mocha-reporter": "^1.0.2", + "karma-requirejs": "^0.2.2", + "mocha": "^2.2.4", + "qunitjs": "~1.11.0", + "requirejs": "^2.1.17" }, "browserify": { "transform": [ diff --git a/test/amd/index.html b/test/amd/index.html new file mode 100644 index 000000000..6677a9ac1 --- /dev/null +++ b/test/amd/index.html @@ -0,0 +1,40 @@ + + + + + gridster.js tests + + + + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/test/amd/index.js b/test/amd/index.js new file mode 100644 index 000000000..48fd56be6 --- /dev/null +++ b/test/amd/index.js @@ -0,0 +1,71 @@ +/*global Gridster:false*/ +/*global chai:false, describe:false, beforeEach:false, afterEach:false, it:false*/ + +require.config({ + baseUrl : '../../', + + paths: { + mocha: 'node_modules/mocha/mocha', + chai: 'node_modules/chai/chai', + jquery: 'node_modules/jquery/dist/jquery', + gridster: 'dist/jquery.gridster' + } + +}); + +require(['jquery'], function($) { + $.noConflict( true ); + + require(['test/amd/index'], function(Gridster) { + mocha.setup('bdd'); + + $(function() { + mocha.run(); + }); + }); +}); + + +define(['chai', 'jquery', 'gridster'], function(chai, $, Gridster) { + 'use strict'; + + var expect = chai.expect; + + describe('AMD support', function() { + describe('Gridster', function() { + it('should not define jQuery as global', function() { + expect(window.$).to.be.undefined; + expect(window.jQuery).to.be.undefined; + }); + + it('should not define Gridster as global', function() { + expect(window.Gridster).to.be.undefined; + expect(window.GridsterDraggable).to.be.undefined; + expect(window.GridsterCoords).to.be.undefined; + expect(window.GridsterCollision).to.be.undefined; + }); + + it('should return Gridster class', function() { + expect(Gridster).to.be.a('function'); + expect(Gridster.name).to.equal('Gridster'); + }); + + it('should define the jquery bridge', function() { + expect($.fn.gridster).to.be.a('function'); + }); + }); + + describe('Draggable', function() { + var Draggable = require('gridster-draggable'); + + it('should not be defined in the global scope', function() { + expect(window.GridsterDraggable).to.be.undefined; + }); + + it('should return the Draggable class', function() { + expect(Draggable.name).to.equal('Draggable'); + }); + }); + }); + +}); diff --git a/test/index.html b/test/index.html new file mode 100644 index 000000000..8496df4ba --- /dev/null +++ b/test/index.html @@ -0,0 +1,29 @@ + + + + + gridster.js tests + + + + + + + + + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/test/index.js b/test/index.js new file mode 100644 index 000000000..72bb503d0 --- /dev/null +++ b/test/index.js @@ -0,0 +1,709 @@ +/*global Gridster:false*/ +/*global chai:false, describe:false, beforeEach:false, afterEach:false, it:false*/ + +'use strict'; + +// add #fixture div when running tests with karma +if (document.body) { + var fixture = document.createElement('div'); fixture.setAttribute('id', 'fixture'); + document.querySelector('#fixture') || document.body.appendChild(fixture); +} + +var expect = chai.expect; +var serialization = { + 'default': function() { + return [ + {name: 'A', col: 1, row: 1, size_x: 1, size_y: 2}, + {name: 'B', col: 2, row: 1, size_x: 3, size_y: 2}, + {name: 'C', col: 5, row: 1, size_x: 3, size_y: 2}, + {name: 'D', col: 8, row: 1, size_x: 2, size_y: 1}, + {name: 'E', col: 1, row: 3, size_x: 4, size_y: 1}, + {name: 'F', col: 10, row: 1, size_x: 1, size_y: 2}, + {name: 'G', col: 8, row: 2, size_x: 2, size_y: 1}, + {name: 'H', col: 5, row: 3, size_x: 3, size_y: 2}, + {name: 'I', col: 8, row: 3, size_x: 1, size_y: 1}, + {name: 'J', col: 9, row: 3, size_x: 2, size_y: 2}, + {name: 'K', col: 1, row: 4, size_x: 1, size_y: 3} + ]; + } +}; + +var SPEED = 100; + +var u = { + pick: function(data, prop) { + return data.map(function(elm) { + return elm[prop]; + }); + }, + + getRandomColor: function() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++) { + color += letters[Math.round(Math.random() * 10)]; + } + return color; + }, + + createGridster: function(config, serialize, fromDom) { + var defaults = { + widget_margins: [5, 5], + widget_base_dimensions: [100, 55], + min_cols: 10, + max_cols: 10 + }; + + serialize || (serialize = serialization.default()); + + var widgets = []; + $.each(serialize, function(i, w) { + widgets.push(['
  • ' + w.name + '
  • ', w.size_x, w.size_y, w.col, w.row]); + }); + + this.$fixture = $('#fixture'); + this.$fixture.html('
      '); + this.$el = $(".gridster > ul"); + + if (fromDom) { + var html = []; + $.each(serialize, function(i, w) { + html.push('
    • ' + w.name + '
    • '); + }); + this.$el.html(html.join('\n')); + } + + this.gridster = this.$el.gridster( + $.extend({}, defaults, config)).data('gridster'); + + if (!fromDom) { + $.each(widgets, function(i, widget) { + this.gridster.add_widget.apply(this.gridster, widget); + }.bind(this)); + } + + this.drag_from_to = u._dragFromTo; + + return this.gridster; + }, + + createEvent: function(type, offset) { + var event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, window, 0, 0, 0, + offset.left, offset.top, false, false, false, false, 0, null); + + return event; + }, + + dispatchEvent: function(elem, type, event) { + if (elem.dispatchEvent) { + elem.dispatchEvent(event); + } else if (elem.fireEvent) { + elem.fireEvent('on' + type, event); + } + }, + + _dragFromTo: function(fromCoords, toCoords) { + var d = $.Deferred(); + var gridster = this.gridster; + var $el; + + function getMousePos(coords) { + var size = gridster.options.widget_base_dimensions; + var margin = gridster.options.widget_margins; + + var left = ((coords[0] - 1) * size[0]) + (margin[0] * ((coords[0] * 2) - 1)); + var top = ((coords[1] - 1) * size[1]) + (margin[1] * ((coords[1] * 2) - 1)); + + var parentOffset = gridster.$wrapper.offset(); + + return { + left: parentOffset.left + left + 20, + top: parentOffset.top + top + 20 + }; + } + + function addPoint(offset) { + $('').css({ + left: offset.left + 'px', + top: offset.top + 'px', + width: '2px', + height: '2px', + background: 'red', + display: 'inline-block', + position: 'absolute', + zIndex: '9999' + }).appendTo('body'); + } + + var from_offset; + + if (fromCoords instanceof $) { + $el = fromCoords; + var offset = $el.offset(); + from_offset = { + left: offset.left + ($el.width() / 2), + top: offset.top + ($el.height() / 2) + }; + } else { + $el = this.gridster.gridmap[fromCoords[0]][fromCoords[1]]; + from_offset = getMousePos(fromCoords); + } + + if (! $el) { + return; + } + + var to_offset = getMousePos(toCoords); + var el = $el.get(0); + + addPoint(from_offset); + addPoint(to_offset); + + // Simulating drag start + var type = 'mousedown'; + var event = u.createEvent(type, from_offset); + u.dispatchEvent(el, type, event); + + // Simulating drop + type = 'mousemove'; + u.dispatchEvent(el, type, u.createEvent(type, { + top: from_offset.top + 2, + left: from_offset.left + 2 + })); + + this.gridster.$el.on('gridster:dragstop gridster:resizestop', function() { + setTimeout(function() { + d.resolveWith(this); + }.bind(this), SPEED); + }.bind(this)); + + var diff_x = to_offset.left - from_offset.left; + var diff_y = to_offset.top - from_offset.top; + var steps = 10; + var step_x = diff_x / steps; + var step_y = diff_y / steps; + + var tmp_offset = { + left: from_offset.left, + top: from_offset.top + }; + + for (var i = 0; i < steps; i++) { + tmp_offset.left += step_x; + tmp_offset.top += step_y; + addPoint(tmp_offset); + u.dispatchEvent(el, type, u.createEvent(type, tmp_offset)); + } + + u.dispatchEvent(el, type, u.createEvent(type, to_offset)); + addPoint(to_offset); + + // Simulating drag end + type = 'mouseup'; + var dragEndEvent = u.createEvent(type, to_offset); + u.dispatchEvent(el, type, dragEndEvent); + + return d.promise(); + } +}; + + + + +describe('gridster.js', function() { + it('should expose Gridster, Draggable, Coords and Collision classes to window', function() { + expect(window.Gridster).to.be.a.function; + expect(window.GridsterDraggable).to.be.a.function; + expect(window.GridsterCoords).to.be.a.function; + expect(window.GridsterCollision).to.be.a.function; + }); + + it('should define the jquery bridge', function() { + expect($.fn.gridster).to.be.a('function'); + }); + + describe('Initialization', function() { + beforeEach(function() { + u.createGridster.call(this, { + serialize_params: function($w, wgd) { + return { + name: $w.text(), col: wgd.col, row: wgd.row, + size_x: wgd.size_x, size_y: wgd.size_y + }; + } + }); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should access the gridster instance', function() { + expect(this.$el.data('gridster')).to.be.an('object'); + expect(this.$el.data('gridster')).to.have.property('$widgets'); + expect(this.$el.data('gridster').$widgets).to.have.length.above(1); + }); + + it('should bind resize event to window', function() { + var events = $._data(window, "events"); + expect(events).to.have.property('resize'); + // both gridster and dragabble resize event handlers + expect(events.resize).to.have.length(2); + }); + + it('should respect the serialized positions', function(done) { + var serialize = this.gridster.serialize(); + expect(serialize).to.deep.equal(serialization.default()); + done(); + }); + }); + + + describe('Instance methods', function() { + describe('Register widgets from DOM', function() { + beforeEach(function() { + u.createGridster.call(this, { + serialize_params: function($w, wgd) { + return { + name: $w.text(), col: wgd.col, row: wgd.row, + size_x: wgd.size_x, size_y: wgd.size_y + }; + } + }, null, true); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should sync DOM widgets with gridster widgets', function() { + expect(this.gridster.$widgets).to.have.length(serialization.default().length); + }); + + it('should respect the serialized positions', function() { + var serialize = this.gridster.serialize(); + expect(serialize).to.deep.equal(serialization.default()); + }); + }); + + + describe('Destroy', function() { + + beforeEach(function() { + u.createGridster.call(this); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should clean data attached but keep the grid on DOM', function() { + this.gridster.destroy(); + expect(this.$el.data('gridster')).to.be.undefined; + expect(document.contains(this.$el[0])).to.be.true; + }); + + it('should clean widgets data even if gridster is not removed from DOM', function() { + var $el = this.gridster.$widgets.eq(1); + this.gridster.destroy(); + expect($el.data('coords')).to.be.undefined; + }); + + it('should remove the grid from the DOM', function() { + this.gridster.destroy(true); + expect(document.contains(this.$el[0])).to.be.false; + }); + + it('should clean widgets data', function() { + var $el = this.gridster.$widgets.eq(1); + this.gridster.destroy(true); + expect($el.data('coords')).to.be.undefined; + }); + + it('should unbind resize event from window', function() { + this.gridster.destroy(true); + expect($._data(window, "events")).to.be.undefined; + }); + + it('should clean gridmap array', function() { + this.gridster.destroy(); + expect(this.gridster.gridmap).to.be.empty; + expect(this.gridster.faux_grid).to.be.empty; + }); + }); + + + describe('Add a widget dynamically', function() { + beforeEach(function() { + u.createGridster.call(this); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should be added to the grid', function() { + this.gridster.add_widget('
    • new
    • ', 2, 2); + var $el = this.gridster.$el.find('.new'); + expect($el).to.have.length(1); + }); + + it('should be positioned in the top/leftmost available space', function() { + this.gridster.remove_widget('[data-col=1][data-row=1]'); + + this.gridster.add_widget('
    • new
    • ', 1, 1); + var $el = this.gridster.$el.find('.new'); + + expect($el.attr('data-col')).to.equal('1'); + expect($el.attr('data-row')).to.equal('1'); + expect(this.gridster.gridmap[1][1][0]).to.equal($el[0]); + }); + + it('should respect the specified dimensions and coords', function() { + this.gridster.add_widget('
    • new
    • ', 2, 2, 2, 1); + var $el = this.gridster.$el.find('.new'); + expect($el.attr('data-col')).to.equal('2'); + expect($el.attr('data-row')).to.equal('1'); + expect($el.attr('data-sizex')).to.equal('2'); + expect($el.attr('data-sizey')).to.equal('2'); + + expect(this.gridster.gridmap[2][1][0]).to.equal($el[0]); + }); + }); + + + describe('Remove a widget', function() { + beforeEach(function() { + u.createGridster.call(this); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should be removed from the grid', function(done) { + var $el = this.gridster.$el.find('[data-col=1][data-row=1]'); + this.gridster.remove_widget($el, false, function() { + expect(this.gridmap[1][1]).to.be.false; + expect(document.contains($el[0])).to.be.false; + done(); + }); + }); + + it('should cause elements below moving up', function(done) { + var $el1 = this.gridster.$el.find('[data-col=1][data-row=1]'); + var $el2 = this.gridster.$el.find('[data-col=2][data-row=1]'); + var $el3 = this.gridster.$el.find('[data-col=1][data-row=3]'); + var silent = false; + this.gridster.remove_widget($el1, silent, function() { + this.remove_widget($el2, silent, function() { + expect($el3.attr('data-col')).to.equal('1'); + expect($el3.attr('data-row')).to.equal('1'); + done(); + }); + }); + }); + + it('shouldn\'t cause elements below moving up (silent=true)', function(done) { + var $el1 = this.gridster.$el.find('[data-col=1][data-row=1]'); + var $el2 = this.gridster.$el.find('[data-col=2][data-row=1]'); + var $el3 = this.gridster.$el.find('[data-col=1][data-row=3]'); + var silent = true; + this.gridster.remove_widget($el1, silent, function() { + this.remove_widget($el2, silent, function() { + expect($el3.attr('data-col')).to.equal('1'); + expect($el3.attr('data-row')).to.equal('3'); + done(); + }); + }); + }); + }); + + + describe('Resize a widget', function() { + beforeEach(function() { + u.createGridster.call(this); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should be resized to the new dimensions', function(done) { + var $el = this.gridster.$el.find('[data-col=1][data-row=1]'); + this.gridster.resize_widget($el, 3, 3, function() { + expect(this.gridmap[2][2][0]).to.equal($el[0]); + done(); + }); + }); + + it('should cause elements below to be displaced', function(done) { + var $el = this.gridster.$el.find('[data-col=1][data-row=1]'); + var $el2 = this.gridster.$el.find('[data-col=2][data-row=1]'); + this.gridster.resize_widget($el, 3, 3, function() { + expect(this.gridmap[2][4][0]).to.equal($el2[0]); + done(); + }); + }); + + it('should respect the resizing limits if specified', function(done) { + var $el = this.gridster.$el.find('[data-col=1][data-row=1]'); + $el.attr({ + 'data-max-sizex': 2, + 'data-max-sizey': 2 + }); + this.gridster.resize_widget($el, 3, 3, function() { + expect(this.gridmap[3][3][0]).to.equal($el[0]); + done(); + }); + }); + }); + }); + + + describe('Class methods', function() { + describe('Gridster.sort_by_row_asc', function() { + it('should sort coords by row in ascending order', function() { + var sorted = Gridster.sort_by_row_asc(serialization.default()); + var result = u.pick(sorted, 'name').join(','); + var expected = 'A,C,D,B,F,G,E,H,I,J,K'; + + expect(result).to.equal(expected); + }); + }); + + describe('Gridster.sort_by_row_and_col_asc', function() { + it('should sort coords by row and col (top-left) in ascending order', function() { + var sorted = Gridster.sort_by_row_and_col_asc(serialization.default()); + var result = u.pick(sorted, 'name').join(','); + var expected = 'A,B,C,D,F,G,E,H,I,J,K'; + + expect(result).to.equal(expected); + }); + }); + + describe('Gridster.sort_by_col_asc', function() { + it('should sort coords by col in ascending order', function() { + var sorted = Gridster.sort_by_col_asc(serialization.default()); + var result = u.pick(sorted, 'name').join(','); + var expected = 'A,E,K,B,C,H,D,G,I,J,F'; + + expect(result).to.equal(expected); + }); + }); + + describe('Gridster.sort_by_row_desc', function() { + it('should sort coords by row in descending order', function() { + var sorted = Gridster.sort_by_row_desc(serialization.default()); + var result = u.pick(sorted, 'name').join(','); + // note that size_y are taken into account + var expected = 'K,J,H,E,I,C,B,G,A,F,D'; + + expect(result).to.equal(expected); + }); + }); + }); + + describe('Interactions', function() { + describe('Drag and drop', function() { + beforeEach(function() { + u.createGridster.call(this); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + describe('move up', function() { + it('should displace the dragged element one cell above', function(done) { + var $el = this.gridster.gridmap[1][3]; + this.drag_from_to([1, 3], [1, 1]).done(function() { + expect(this.gridster.gridmap[1][1][0]).to.equal($el[0]); + done(); + }); + }); + + it('should displace elements above under the dragged element', function(done) { + var $above1 = this.gridster.gridmap[1][1]; + var $above2 = this.gridster.gridmap[2][1]; + + this.drag_from_to([1, 3], [1, 1]).done(function() { + expect(this.gridster.gridmap[1][2][0]).to.equal($above1[0]); + expect(this.gridster.gridmap[2][2][0]).to.equal($above2[0]); + done(); + }); + }); + }); + + describe('move down', function() { + it('should displace the dragged element one cell below', function(done) { + var $el = this.gridster.gridmap[5][1]; + this.drag_from_to([5, 1], [5, 6]).done(function() { + expect(this.gridster.gridmap[5][4]).to.be.ok; + expect(this.gridster.gridmap[5][4][0]).to.equal($el[0]); + done(); + }); + }); + + it('should displace elements below on the top of the dragged element', function(done) { + var $below1 = this.gridster.gridmap[5][3]; + + this.drag_from_to([5, 1], [5, 6]).done(function() { + expect(this.gridster.gridmap[5][1][0]).to.equal($below1[0]); + done(); + }); + }); + }); + + describe('move left', function() { + it('should displace the dragged element one cell left', function(done) { + var $el = this.gridster.gridmap[2][1]; + this.drag_from_to([2, 1], [1, 1]).done(function() { + expect(this.gridster.gridmap[1][1]).to.be.ok; + expect(this.gridster.gridmap[1][1][0]).to.equal($el[0]); + done(); + }); + }); + + it('should displace the item on the left under the dragged element', function(done) { + var $left = this.gridster.gridmap[1][1]; + var $left_bottom = this.gridster.gridmap[1][3]; + this.drag_from_to([2, 1], [1, 1]).done(function() { + expect(this.gridster.gridmap[1][3]).to.be.ok; + expect(this.gridster.gridmap[1][3][0]).to.equal($left[0]); + expect(this.gridster.gridmap[1][5]).to.be.ok; + expect(this.gridster.gridmap[1][5][0]).to.equal($left_bottom[0]); + done(); + }); + }); + }); + + describe('move right', function() { + it('should displace the dragged element one cell right', function(done) { + var $el = this.gridster.gridmap[4][3]; + this.drag_from_to([4, 3], [5, 3]).done(function() { + expect(this.gridster.gridmap[5][3]).to.be.ok; + expect(this.gridster.gridmap[5][3][0]).to.equal($el[0]); + done(); + }); + }); + + it('should displace the item below one cell up', function(done) { + var $below = this.gridster.gridmap[1][4]; + this.drag_from_to([4, 3], [5, 3]).done(function() { + expect(this.gridster.gridmap[1][3]).to.be.ok; + expect(this.gridster.gridmap[1][3][0]).to.equal($below[0]); + done(); + }); + }); + + it('should displace the item on the right one cell down', function(done) { + var $right = this.gridster.gridmap[5][3]; + this.drag_from_to([4, 3], [5, 3]).done(function() { + expect(this.gridster.gridmap[5][4]).to.be.ok; + expect(this.gridster.gridmap[5][4][0]).to.equal($right[0]); + done(); + }); + }); + }); + }); + + + describe('Resize', function() { + describe('axis', function() { + beforeEach(function() { + u.createGridster.call(this, { + resize: { + enabled: true, + axes: ['x', 'y', 'both'] + } + }); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should resize in both x/y axis', function(done) { + var $el = this.gridster.gridmap[1][1]; + var $handle = $el.find('.gs-resize-handle-both'); + this.drag_from_to($handle, [7, 4]).done(function() { + expect($el.attr('data-sizex')).to.equal('7'); + expect($el.attr('data-sizey')).to.equal('4'); + this.drag_from_to($handle, [1, 1]).done(function() { + expect($el.attr('data-sizex')).to.equal('1'); + expect($el.attr('data-sizey')).to.equal('1'); + done(); + }); + }); + }); + + it('should resize horizontally', function(done) { + var $el = this.gridster.gridmap[1][1]; + var $handle = $el.find('.gs-resize-handle-x'); + this.drag_from_to($handle, [6, 1]).done(function() { + expect($el.attr('data-sizex')).to.equal('6'); + expect($el.attr('data-sizey')).to.equal('2'); + this.drag_from_to($handle, [1, 1]).done(function() { + expect($el.attr('data-sizex')).to.equal('1'); + expect($el.attr('data-sizey')).to.equal('2'); + done(); + }); + }); + }); + + it('should resize vertically', function(done) { + var $el = this.gridster.gridmap[1][1]; + var $handle = $el.find('.gs-resize-handle-y'); + this.drag_from_to($handle, [1, 6]).done(function() { + expect($el.attr('data-sizex')).to.equal('1'); + expect($el.attr('data-sizey')).to.equal('6'); + this.drag_from_to($handle, [1, 1]).done(function() { + done(); + }); + }); + }); + }); + + describe('max/min size', function() { + beforeEach(function() { + u.createGridster.call(this, { + resize: { + enabled: true, + max_size: [4, 4], + min_size: [2, 2] + } + }); + }); + + afterEach(function() { + this.gridster.destroy(true); + }); + + it('should respect specified resize.max_size option', function(done) { + var $el = this.gridster.gridmap[1][1]; + var $handle = $el.find('.gs-resize-handle-both'); + this.drag_from_to($handle, [8, 8]).done(function() { + expect($el.attr('data-sizex')).to.equal('4'); + expect($el.attr('data-sizey')).to.equal('4'); + done(); + }); + }); + + it('should respect specified resize.min_size option', function(done) { + var $el = this.gridster.gridmap[1][1]; + var $handle = $el.find('.gs-resize-handle-both'); + this.drag_from_to($handle, [8, 8]).done(function() { + this.drag_from_to($handle, [1, 1]).done(function() { + expect($el.attr('data-sizex')).to.equal('2'); + expect($el.attr('data-sizey')).to.equal('2'); + done(); + }); + }); + }); + }); + }); + }); + +}); \ No newline at end of file diff --git a/test/jquery.gridder.html b/test/jquery.gridder.html deleted file mode 100644 index 1750f0a4d..000000000 --- a/test/jquery.gridder.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - gridster.js Test Suite - - - - - - - - - - - - - - - - - - - - - - -

      gridster.js Test Suite

      -

      -
      -

      -
        -
        - -
        -
          -
        • -
        • -
        • -
        • - -
        • -
        • -
        • -
        • -
        • - -
        • - - -
        • -
        • -
        • -
        • - -
        • -
        • - -
        • - -
        -
        -
        - - - - diff --git a/test/jquery.gridder_test.js b/test/jquery.gridder_test.js deleted file mode 100644 index 2df25e775..000000000 --- a/test/jquery.gridder_test.js +++ /dev/null @@ -1,38 +0,0 @@ -/*global QUnit:false, module:false, test:false, asyncTest:false, expect:false*/ -/*global start:false, stop:false ok:false, equal:false, notEqual:false, deepEqual:false*/ -/*global notDeepEqual:false, strictEqual:false, notStrictEqual:false, raises:false*/ -(function($) { - - /* - ======== A Handy Little QUnit Reference ======== - http://docs.jquery.com/QUnit - - Test methods: - expect(numAssertions) - stop(increment) - start(decrement) - Test assertions: - ok(value, [message]) - equal(actual, expected, [message]) - notEqual(actual, expected, [message]) - deepEqual(actual, expected, [message]) - notDeepEqual(actual, expected, [message]) - strictEqual(actual, expected, [message]) - notStrictEqual(actual, expected, [message]) - raises(block, [expected], [message]) - */ - - module('jQuery#gridster', { - setup: function() { - - this.el = $('#qunit-fixture').find(".wrapper ul"); - - } - }); - - // test('is chainable', 1, function() { - // // Not a bad test to run on collection methods. - // strictEqual(this.el, this.el.gridster(), 'should be chaninable'); - // }); - -}(jQuery)); diff --git a/test/karma.conf.js b/test/karma.conf.js new file mode 100644 index 000000000..6b3c25436 --- /dev/null +++ b/test/karma.conf.js @@ -0,0 +1,39 @@ +module.exports = function(config) { + 'use strict'; + + config.set({ + frameworks: ['mocha', 'chai'], + + client: { + captureConsole: true, + mocha: { + reporter: 'html', // change Karma's debug.html to the mocha web reporter + ui: 'bdd' + } + }, + + files: [ + {pattern: 'node_modules/jquery/dist/jquery.js', include: true}, + {pattern: 'dist/jquery.gridster.css', include: true}, + {pattern: 'dist/jquery.gridster.js', include: true}, + {pattern: 'test/lib/test.css', include: true}, + 'test/index.js' + ], + + reporters: ['mocha'], + + // level of logging (config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG) + logLevel: config.LOG_DEBUG, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + colors: true, + + // Start these browsers, currently available: + // Chrome, ChromeCanary, Firefox, Opera, Safari (only Mac), PhantomJS, IE (only Windows) + browsers: ['Chrome'], + + singleRun: true + }); +}; \ No newline at end of file diff --git a/test/lib/test.css b/test/lib/test.css new file mode 100644 index 000000000..7465bd5a6 --- /dev/null +++ b/test/lib/test.css @@ -0,0 +1,25 @@ +#mocha { + box-sizing: border-box; + float: left; + width: 50%; + margin: 0; + padding: 20px; +} + +#mocha-stats { + position: static; +} + +#fixture { + width: 50%; + float: right; +} + + +.gridster ul { + list-style: none; +} + +.gridster li { + background-color: #DDD; +} \ No newline at end of file diff --git a/test/lib/test_utils.js b/test/lib/test_utils.js new file mode 100644 index 000000000..0c7c6d9de --- /dev/null +++ b/test/lib/test_utils.js @@ -0,0 +1,198 @@ +'use strict'; + +window.serialization = { + 'default': function() { + return [ + {name: 'A', col: 1, row: 1, size_x: 1, size_y: 2}, + {name: 'B', col: 2, row: 1, size_x: 3, size_y: 2}, + {name: 'C', col: 5, row: 1, size_x: 3, size_y: 2}, + {name: 'D', col: 8, row: 1, size_x: 2, size_y: 1}, + {name: 'E', col: 1, row: 3, size_x: 4, size_y: 1}, + {name: 'F', col: 10, row: 1, size_x: 1, size_y: 2}, + {name: 'G', col: 8, row: 2, size_x: 2, size_y: 1}, + {name: 'H', col: 5, row: 3, size_x: 3, size_y: 2}, + {name: 'I', col: 8, row: 3, size_x: 1, size_y: 1}, + {name: 'J', col: 9, row: 3, size_x: 2, size_y: 2}, + {name: 'K', col: 1, row: 4, size_x: 1, size_y: 3} + ]; + } +}; + + +window.u = { + pick: function(data, prop) { + return data.map(function(elm) { + return elm[prop]; + }); + }, + + getRandomColor: function() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++) { + color += letters[Math.round(Math.random() * 10)]; + } + return color; + }, + + createGridster: function(config, serialize, fromDom) { + var defaults = { + widget_margins: [5, 5], + widget_base_dimensions: [100, 55], + min_cols: 10, + max_cols: 10 + }; + + serialize || (serialize = window.serialization.default()); + + var widgets = []; + $.each(serialize, function(i, w) { + widgets.push(['
      1. ' + w.name + '
      2. ', w.size_x, w.size_y, w.col, w.row]); + }); + + this.$fixture = $('#fixture'); + this.$fixture.html('
          '); + this.$el = $(".gridster > ul"); + + if (fromDom) { + var html = []; + $.each(serialize, function(i, w) { + html.push('
        • ' + w.name + '
        • '); + }); + this.$el.html(html.join('\n')); + } + + this.gridster = this.$el.gridster( + $.extend({}, defaults, config)).data('gridster'); + + if (!fromDom) { + $.each(widgets, function(i, widget) { + this.gridster.add_widget.apply(this.gridster, widget); + }.bind(this)); + } + + this.drag_from_to = u._dragFromTo; + + return this.gridster; + }, + + createEvent: function(type, offset) { + var event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, window, 0, 0, 0, + offset.left, offset.top, false, false, false, false, 0, null); + + return event; + }, + + dispatchEvent: function(elem, type, event) { + if (elem.dispatchEvent) { + elem.dispatchEvent(event); + } else if (elem.fireEvent) { + elem.fireEvent('on' + type, event); + } + }, + + _dragFromTo: function(fromCoords, toCoords) { + var d = $.Deferred(); + var gridster = this.gridster; + var $el; + + function getMousePos(coords) { + var size = gridster.options.widget_base_dimensions; + var margin = gridster.options.widget_margins; + + var left = ((coords[0] - 1) * size[0]) + (margin[0] * ((coords[0] * 2) - 1)); + var top = ((coords[1] - 1) * size[1]) + (margin[1] * ((coords[1] * 2) - 1)); + + var parentOffset = gridster.$wrapper.offset(); + + return { + left: parentOffset.left + left + 20, + top: parentOffset.top + top + 20 + }; + } + + function addPoint(offset) { + $('').css({ + left: offset.left + 'px', + top: offset.top + 'px', + width: '2px', + height: '2px', + background: 'red', + display: 'inline-block', + position: 'absolute', + zIndex: '9999' + }).appendTo('body'); + } + + var from_offset; + + if (fromCoords instanceof $) { + $el = fromCoords; + var offset = $el.offset(); + from_offset = { + left: offset.left + ($el.width() / 2), + top: offset.top + ($el.height() / 2) + }; + } else { + $el = this.gridster.gridmap[fromCoords[0]][fromCoords[1]]; + from_offset = getMousePos(fromCoords); + } + + if (! $el) { + return; + } + + var to_offset = getMousePos(toCoords); + var el = $el.get(0); + + addPoint(from_offset); + addPoint(to_offset); + + // Simulating drag start + var type = 'mousedown'; + var event = u.createEvent(type, from_offset); + u.dispatchEvent(el, type, event); + + // Simulating drop + type = 'mousemove'; + u.dispatchEvent(el, type, u.createEvent(type, { + top: from_offset.top + 2, + left: from_offset.left + 2 + })); + + this.gridster.$el.on('gridster:dragstop gridster:resizestop', function() { + setTimeout(function() { + d.resolveWith(this); + }.bind(this), SPEED); + }.bind(this)); + + var diff_x = to_offset.left - from_offset.left; + var diff_y = to_offset.top - from_offset.top; + var steps = 10; + var step_x = diff_x / steps; + var step_y = diff_y / steps; + + var tmp_offset = { + left: from_offset.left, + top: from_offset.top + }; + + for (var i = 0; i < steps; i++) { + tmp_offset.left += step_x; + tmp_offset.top += step_y; + addPoint(tmp_offset); + u.dispatchEvent(el, type, u.createEvent(type, tmp_offset)); + } + + u.dispatchEvent(el, type, u.createEvent(type, to_offset)); + addPoint(to_offset); + + // Simulating drag end + type = 'mouseup'; + var dragEndEvent = u.createEvent(type, to_offset); + u.dispatchEvent(el, type, dragEndEvent); + + return d.promise(); + } +}; From 7de5bbabc0a01e8188a56881782dc74d6bf111d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 17:45:11 +0200 Subject: [PATCH 13/17] feat(gridster): add config to set custom show/hide widget methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit by default jQuery’s `hide` and `show` methods are used. You could also use fadeIn/fadeOut or write your own kind of jQuery plugin like `$.fn.showInAFancyWay` and use `showInAFancyWay` as the value in the show_method config option. If you want to keep the previos behaviour, you need to set `hide_method` option to `’fadeOut’` Breaking Changes `remove_widget` and `remove_all_widgets` methods not return a promise instead of the gridster instance --- src/jquery.gridster.js | 47 ++++++++++++++++++++++++++---------------- test/index.js | 27 ++++++++++++------------ 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 13d43820b..1d0a12aa1 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -35,6 +35,8 @@ autogenerate_stylesheet: true, avoid_overlapped_widgets: true, auto_init: true, + show_method: 'show', + hide_method: 'hide', serialize_params: function($w, wgd) { return { col: wgd.col, @@ -372,7 +374,7 @@ this.drag_api.set_limits(this.cols * this.min_widget_width); - return $w.fadeIn(); + return $w[this.options.show_method](); }; @@ -721,9 +723,10 @@ * @param {Boolean|Function} silent If true, widgets below the removed one * will not move up. If a Function is passed it will be used as callback. * @param {Function} callback Function executed when the widget is removed. - * @return {Class} Returns the instance of the Gridster Class. + * @return {Promise} Returns a jQuery promise. */ fn.remove_widget = function(el, silent, callback) { + var d = $.Deferred(); var $el = el instanceof $ ? el : $(el); var wgd = $el.coords().grid; @@ -740,23 +743,24 @@ this.remove_from_gridmap(wgd); - $el.fadeOut($.proxy(function() { - $el.remove(); + $el[this.options.hide_method]({ + always: $.proxy(function() { + $el.remove(); - if (!silent) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), wgd.size_y ); - }, this)); - } + if (!silent) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), wgd.size_y ); + }, this)); + } - this.set_dom_grid_height(); + this.set_dom_grid_height(); - if (callback) { - callback.call(this, el); - } - }, this)); + callback && callback.call(this, el); + d.resolveWith(this); + }, this) + }); - return this; + return d.promise(); }; @@ -765,14 +769,21 @@ * * @method remove_all_widgets * @param {Function} callback Function executed for each widget removed. - * @return {Class} Returns the instance of the Gridster Class. + * @return {Promise} Returns a jQuery promise. */ fn.remove_all_widgets = function(callback) { + var d = $.Deferred(); + var promises = []; this.$widgets.each($.proxy(function(i, el){ - this.remove_widget(el, true, callback); + promises.push(this.remove_widget(el, true)); }, this)); - return this; + $.when.apply(null, promises).done($.proxy(function() { + callback && callback.call(this); + d.resolveWith(this); + }, this)); + + return d.promise(); }; diff --git a/test/index.js b/test/index.js index 72bb503d0..90bf244e2 100644 --- a/test/index.js +++ b/test/index.js @@ -349,15 +349,16 @@ describe('gridster.js', function() { expect($el).to.have.length(1); }); - it('should be positioned in the top/leftmost available space', function() { - this.gridster.remove_widget('[data-col=1][data-row=1]'); + it('should be positioned in the top/leftmost available space', function(done) { + this.gridster.remove_widget('[data-col=1][data-row=1]').done(function() { + this.gridster.add_widget('
        • new
        • ', 1, 1); - this.gridster.add_widget('
        • new
        • ', 1, 1); - var $el = this.gridster.$el.find('.new'); - - expect($el.attr('data-col')).to.equal('1'); - expect($el.attr('data-row')).to.equal('1'); - expect(this.gridster.gridmap[1][1][0]).to.equal($el[0]); + var $el = this.gridster.$el.find('.new'); + expect($el.attr('data-col')).to.equal('1'); + expect($el.attr('data-row')).to.equal('1'); + expect(this.gridster.gridmap[1][1][0]).to.equal($el[0]); + done(); + }.bind(this)); }); it('should respect the specified dimensions and coords', function() { @@ -384,7 +385,7 @@ describe('gridster.js', function() { it('should be removed from the grid', function(done) { var $el = this.gridster.$el.find('[data-col=1][data-row=1]'); - this.gridster.remove_widget($el, false, function() { + this.gridster.remove_widget($el, false).done(function() { expect(this.gridmap[1][1]).to.be.false; expect(document.contains($el[0])).to.be.false; done(); @@ -396,8 +397,8 @@ describe('gridster.js', function() { var $el2 = this.gridster.$el.find('[data-col=2][data-row=1]'); var $el3 = this.gridster.$el.find('[data-col=1][data-row=3]'); var silent = false; - this.gridster.remove_widget($el1, silent, function() { - this.remove_widget($el2, silent, function() { + this.gridster.remove_widget($el1, silent).done(function() { + this.remove_widget($el2, silent).done(function() { expect($el3.attr('data-col')).to.equal('1'); expect($el3.attr('data-row')).to.equal('1'); done(); @@ -410,8 +411,8 @@ describe('gridster.js', function() { var $el2 = this.gridster.$el.find('[data-col=2][data-row=1]'); var $el3 = this.gridster.$el.find('[data-col=1][data-row=3]'); var silent = true; - this.gridster.remove_widget($el1, silent, function() { - this.remove_widget($el2, silent, function() { + this.gridster.remove_widget($el1, silent).done(function() { + this.remove_widget($el2, silent).done(function() { expect($el3.attr('data-col')).to.equal('1'); expect($el3.attr('data-row')).to.equal('3'); done(); From cda560f4f3ca616d03d1e3230cd2ef4e69876d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 18:54:53 +0200 Subject: [PATCH 14/17] fix(gridster): `resize_widget` also accepts HTMLElements for consistency with the `remove_widget` --- src/jquery.gridster.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 1d0a12aa1..79bd94151 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -444,8 +444,8 @@ * Change the size of a widget. Width is limited to the current grid width. * * @method resize_widget - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. + * * @param {HTMLElement|$HTMLElement} el The widget HTMLElement, jQuery + * wrapped or not, you want to resize. * @param {Number} size_x The number of columns that will occupy the widget. * By default size_x is limited to the space available from * the column where the widget begins, until the last column to the right. @@ -453,7 +453,8 @@ * @param {Function} [callback] Function executed when the widget is removed. * @return {HTMLElement} Returns $widget. */ - fn.resize_widget = function($widget, size_x, size_y, callback) { + fn.resize_widget = function(el, size_x, size_y, callback) { + var $widget = el instanceof $ ? el : $(el); var wgd = $widget.coords().grid; var col = wgd.col; var max_cols = this.options.max_cols; @@ -719,7 +720,8 @@ * Remove a widget from the grid. * * @method remove_widget - * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {HTMLElement|$HTMLElement} el The widget HTMLElement, jQuery wrapped + * or not, you want to remove. * @param {Boolean|Function} silent If true, widgets below the removed one * will not move up. If a Function is passed it will be used as callback. * @param {Function} callback Function executed when the widget is removed. From 576b5ae3f0461b048d8ff9463509b860ffa8b194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 19:12:00 +0200 Subject: [PATCH 15/17] fix(gridster): ensure coords instances are destroyed on widgets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …when removing a widget, or when destroying the gridster instance. Before this change, if you called destroy without `remove` set to true, coords instances was leaking jQuery data objects. Also nullified the gridmap, and faux_grid arrays. --- src/jquery.gridster.js | 6 ++++++ test/index.js | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 79bd94151..e093b7d56 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -747,6 +747,7 @@ $el[this.options.hide_method]({ always: $.proxy(function() { + $el.coords().destroy(); $el.remove(); if (!silent) { @@ -3123,6 +3124,11 @@ */ fn.destroy = function(remove) { this.$el.removeData('gridster'); + this.$widgets.each($.proxy(function(i, el){ + $(el).coords().destroy(); + }, this)); + this.gridmap = null; + this.faux_grid = null; // remove bound callback on window resize $(window).unbind('.gridster'); diff --git a/test/index.js b/test/index.js index 90bf244e2..b0082fa93 100644 --- a/test/index.js +++ b/test/index.js @@ -328,8 +328,8 @@ describe('gridster.js', function() { it('should clean gridmap array', function() { this.gridster.destroy(); - expect(this.gridster.gridmap).to.be.empty; - expect(this.gridster.faux_grid).to.be.empty; + expect(this.gridster.gridmap).to.not.have.length; + expect(this.gridster.faux_grid).to.not.have.length; }); }); From 3006d4f3934a6ed9e6cb5ffa38eb3868c08448b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 20:22:59 +0200 Subject: [PATCH 16/17] style(jshint): add jshint config and setup grunt task --- .jshintrc | 100 ++++++++++++++ Gruntfile.js | 360 ++++++++++++++++++++++++++------------------------- package.json | 3 +- 3 files changed, 285 insertions(+), 178 deletions(-) create mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 000000000..4ce55d0f0 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,100 @@ +{ + // http://www.jshint.com/docs/ + // Based on node-jshint@2.x.x + + // ENFORCING OPTIONS + // These options tell JSHint to be more strict towards your code. Use them if + // you want to allow only a safe subset of JavaScript—very useful when your + // codebase is shared with a big number of developers with different skill + // levels. + + "bitwise": true, //prohibits the use of bitwise operators such as ^ (XOR), | (OR) and others + "camelcase": false, //force all variable names to use either camelCase style or UPPER_CASE with underscores + "curly": true, //requires you to always put curly braces around blocks in loops and conditionals + "eqeqeq": true, //prohibits the use of == and != in favor of === and !== + "es3": false, //tells JSHint that your code needs to adhere to ECMAScript 3 specification + "forin": true, //requires all `for in` loops to filter object's items with `hasOwnProperty()` + "immed": true, //prohibits the use of immediate function invocations without wrapping them in parentheses + "indent": 2, //enforces specific tab width + "latedef": true, //prohibits the use of a variable before it was defined + "newcap": true, //requires you to capitalize names of constructor functions + "noarg": true, //prohibits the use of `arguments.caller` and `arguments.callee` + "noempty": true, //warns when you have an empty block in your code + "nonew": true, //prohibits the use of constructor functions for side-effects + "plusplus": false, //prohibits the use of unary increment and decrement operators + "quotmark": true, //enforces the consistency of quotation marks used throughout your code + "undef": true, //prohibits the use of explicitly undeclared variables + "unused": "vars", //warns when you define and never use your variables + "strict": true, //requires all functions to run in ECMAScript 5's strict mode + "trailing": true, //makes it an error to leave a trailing whitespace in your code + "maxparams": false, //set the max number of formal parameters allowed per function + "maxdepth": 3, //control how nested do you want your blocks to be + "maxstatements": false, //set the max number of statements allowed per function + "maxcomplexity": false, //control cyclomatic complexity throughout your code + "maxlen": 99, //set the maximum length of a line + + // RELAXING OPTIONS + // These options allow you to suppress certain types of warnings. Use them + // only if you are absolutely positive that you know what you are doing. + + "asi": false, //suppresses warnings about missing semicolons + "boss": false, //suppresses warnings about the use of assignments in cases where comparisons are expected + "debug": false, //suppresses warnings about the debugger statements in your code + "eqnull": false, //suppresses warnings about == null comparisons + "esnext": false, //your code uses ES.next specific features such as const + "evil": false, //suppresses warnings about the use of eval + "expr": true, //suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls + "funcscope": false, //suppresses warnings about declaring variables inside of control structures while accessing them later from the outside + "globalstrict": false, //suppresses warnings about the use of global strict mode + "iterator": false, //suppresses warnings about the `__iterator__` property + "lastsemic": false, //suppresses warnings about missing semicolons, but only when the semicolon is omitted for the last statement in a one-line block + "laxbreak": false, //suppresses most of the warnings about possibly unsafe line breakings in your code + "laxcomma": false, //suppresses warnings about comma-first coding style + "loopfunc": false, //suppresses warnings about functions inside of loops + "moz": false, //tells JSHint that your code uses Mozilla JavaScript extensions + "multistr": false, //suppresses warnings about multi-line strings + "proto": false, //suppresses warnings about the `__proto__` property + "scripturl": false, //suppresses warnings about the use of script-targeted URLs—such as `javascript:...` + "smarttabs": false, //suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only + "shadow": false, //suppresses warnings about variable shadowing + "sub": false, //suppresses warnings about using `[]` notation when it can be expressed in dot notation + "supernew": false, //suppresses warnings about "weird" constructions like `new function () { ... }` and `new Object;` + "validthis": false, //suppresses warnings about possible strict violations when the code is running in strict mode and you use `this` in a non-constructor function + + // ENVIRONMENTS + // These options pre-define global variables that are exposed by popular + // JavaScript libraries and runtime environments—such as browser or node.js. + // Essentially they are shortcuts for explicit declarations like + // /*global $:false, jQuery:false */ + + "browser": true, //defines globals exposed by modern browsers + "couch": false, //defines globals exposed by CouchDB + "devel": true, //defines globals that are usually used for logging poor-man's debugging: `console`, `alert`, etc. + "dojo": false, //defines globals exposed by the Dojo Toolkit + "jquery": true, //defines globals exposed by the jQuery JavaScript library + "mootools": false, //defines globals exposed by the MooTools JavaScript framework + "node": true, //defines globals available when your code is running inside of the Node runtime environment + "nonstandard": false, //defines non-standard but widely adopted globals such as `escape` and `unescape` + "phantom": false, //defines globals available when your core is running inside of the PhantomJS runtime environment + "prototypejs": false, //defines globals exposed by the Prototype JavaScript framework + "rhino": false, //defines globals available when your code is running inside of the Rhino runtime environment + "worker": true, //defines globals available when your code is running inside of a Web Worker + "wsh": false, //defines globals available when your code is running as a script for the Windows Script Host + "yui": false, //defines globals exposed by the YUI JavaScript framework + + "globals": { + "define": false, + "throttle": false, + "delay": false, + "debounce": false + }, + + // LEGACY + // These options are legacy from JSLint. Aside from bug fixes they will not + // be improved in any way and might be removed at any point. + + "nomen": false, //disallows the use of dangling `_` in variables + "onevar": false, //allows only one `var` statement per function + "passfail": false, //makes JSHint stop on the first error or warning + "white": false //make JSHint check your source code against Douglas Crockford's JavaScript coding style +} diff --git a/Gruntfile.js b/Gruntfile.js index a1176b4d4..acac6555c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,190 +1,196 @@ -/*global module:false*/ +// global module:false module.exports = function(grunt) { - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - meta: { - banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("yyyy-mm-dd") %>\n' + - '<%= pkg.homepage ? "* " + pkg.homepage : "" %>\n' + - '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + - ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n\n', + 'use strict'; - minibanner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("yyyy-mm-dd") %> - ' + - '<%= pkg.homepage ? "* " + pkg.homepage + " - " : "" %>' + - 'Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + - ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */ ' - }, - concat: { - options: { - stripBanners: true, - banner: '<%= meta.banner %>' - }, - dist_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js'], - dest: 'dist/jquery.<%= pkg.name %>.js' - }, + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), - dist_extras_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js', 'src/jquery.<%= pkg.name %>.extras.js'], - dest: 'dist/jquery.<%= pkg.name %>.with-extras.js' - }, + meta: { + banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + + '<%= pkg.homepage ? "* " + pkg.homepage : "" %>\n' + + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n\n', - dist_css: { - src: ['src/jquery.<%= pkg.name %>.css'], - dest: 'dist/jquery.<%= pkg.name %>.css' - }, - - dist_demo_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js'], - dest: 'gh-pages/dist/jquery.<%= pkg.name %>.js' - }, - - dist_extras_demo_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js', 'src/jquery.<%= pkg.name %>.extras.js'], - dest: 'gh-pages/dist/jquery.<%= pkg.name %>.with-extras.js' - }, - - dist_demo_css: { - src: ['src/jquery.<%= pkg.name %>.css'], - dest: 'gh-pages/dist/jquery.<%= pkg.name %>.css' - } - }, - uglify: { - options: { - banner: '<%= meta.minibanner %>' - }, - dist: { - files: { - 'dist/jquery.<%= pkg.name %>.min.js': ['<%= concat.dist_js.dest %>'] - } - }, - - dist_extras: { - files: { - 'dist/jquery.<%= pkg.name %>.with-extras.min.js': ['<%= concat.dist_extras_js.dest %>'] - } - }, - - dist_demo: { - files: { - 'gh-pages/dist/jquery.<%= pkg.name %>.min.js': ['<%= concat.dist_js.dest %>'], - } - }, - - dist_extras_demo: { - files: { - 'gh-pages/dist/jquery.<%= pkg.name %>.with-extras.min.js': ['<%= concat.dist_extras_js.dest %>'] - } - } - }, - cssmin: { - compress: { - options: { - keepSpecialComments: 0, - banner: '<%= meta.minibanner %>' + minibanner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %> - ' + + '<%= pkg.homepage ? "* " + pkg.homepage + " - " : "" %>' + + 'Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */ ' }, - files: { - "dist/jquery.<%= pkg.name %>.min.css": ["dist/jquery.<%= pkg.name %>.css"], - "gh-pages/dist/jquery.<%= pkg.name %>.min.css": ["dist/jquery.<%= pkg.name %>.css"] + + concat: { + options: { + stripBanners: true, + banner: '<%= meta.banner %>' + }, + dist_js: { + src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', + 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js'], + dest: 'dist/jquery.<%= pkg.name %>.js' + }, + dist_extras_js: { + src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', + 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js', + 'src/jquery.<%= pkg.name %>.extras.js'], + dest: 'dist/jquery.<%= pkg.name %>.with-extras.js' + }, + dist_css: { + src: ['src/jquery.<%= pkg.name %>.css'], + dest: 'dist/jquery.<%= pkg.name %>.css' + }, + dist_demo_js: { + src: ['src/jquery.coords.js', 'src/jquery.collision.js', + 'src/utils.js', 'src/jquery.draggable.js', + 'src/jquery.<%= pkg.name %>.js'], + dest: 'gh-pages/dist/jquery.<%= pkg.name %>.js' + }, + dist_extras_demo_js: { + src: ['src/jquery.coords.js', 'src/jquery.collision.js', + 'src/utils.js', 'src/jquery.draggable.js', + 'src/jquery.<%= pkg.name %>.js', + 'src/jquery.<%= pkg.name %>.extras.js'], + dest: 'gh-pages/dist/jquery.<%= pkg.name %>.with-extras.js' + }, + dist_demo_css: { + src: ['src/jquery.<%= pkg.name %>.css'], + dest: 'gh-pages/dist/jquery.<%= pkg.name %>.css' + } + }, + + uglify: { + options: { + banner: '<%= meta.minibanner %>' + }, + dist: { + files: { + 'dist/jquery.<%= pkg.name %>.min.js': ['<%= concat.dist_js.dest %>'] + } + }, + dist_extras: { + files: { + 'dist/jquery.<%= pkg.name %>.with-extras.min.js': [ + '<%= concat.dist_extras_js.dest %>' + ] + } + }, + dist_demo: { + files: { + 'gh-pages/dist/jquery.<%= pkg.name %>.min.js': [ + '<%= concat.dist_js.dest %>' + ], + } + }, + dist_extras_demo: { + files: { + 'gh-pages/dist/jquery.<%= pkg.name %>.with-extras.min.js': [ + '<%= concat.dist_extras_js.dest %>' + ] + } + } + }, + cssmin: { + compress: { + options: { + keepSpecialComments: 0, + banner: '<%= meta.minibanner %>' + }, + files: { + 'dist/jquery.<%= pkg.name %>.min.css': [ + 'dist/jquery.<%= pkg.name %>.css' + ], + 'gh-pages/dist/jquery.<%= pkg.name %>.min.css': [ + 'dist/jquery.<%= pkg.name %>.css' + ] + } + } + }, + + jshint: { + files: ['Gruntfile.js', 'src/**.js'], + options: { + jshintrc: true, + reporter: require('jshint-stylish') + } + }, + + yuidoc: { + compile: { + name: 'gridster.js', + description: 'gridster.js, a drag-and-drop multi-column jQuery grid plugin', + version: '0.1.0', + url: 'http://gridster.net/', + logo: 'https://ducksboard.com/static/images/svg/logo-ducksboard-black-small.svg', + options: { + paths: 'src/', + outdir: 'gh-pages/docs/' + } + } + }, + + bump: { + options: { + files: ['package.json'], + updateConfigs: ['pkg'], + commit: true, + commitMessage: 'Release v%VERSION%', + commitFiles: ['package.json', 'CHANGELOG.md', 'dist/'], // '-a' for all files + createTag: true, + tagName: 'v%VERSION%', + tagMessage: 'Version %VERSION%', + push: false, + pushTo: 'origin', + // options to use with '$ git describe' + gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' + } + }, + + changelog: { + options: { + dest: 'CHANGELOG.md' + } + }, + + watch: { + files: ['libs/*.js', 'src/*.js', 'src/*.css', 'Gruntfile.js'], + tasks: ['concat', 'uglify', 'cssmin'] + }, + + karma: { + gridster: { + configFile: 'test/karma.conf.js' + } } - } - }, - jshint: { - files: ['grunt.js', 'src/**/*.js', 'test/**/*.js'] - }, - watch: { - files: ['<%= lint.files %>', 'src/jquery.<%= pkg.name %>.css'], - tasks: 'min concat' - }, - jshint: { - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - boss: true, - eqnull: true, - browser: true - }, - globals: { - jQuery: true - } - }, - yuidoc: { - compile: { - "name": 'gridster.js', - "description": 'gridster.js, a drag-and-drop multi-column jQuery grid plugin', - "version": '0.1.0', - "url": 'http://gridster.net/', - "logo": 'https://ducksboard.com/static/images/svg/logo-ducksboard-black-small.svg', - options: { - paths: "src/", - outdir: "gh-pages/docs/" - } - } - }, - - bump: { - options: { - files: ['package.json'], - updateConfigs: ['pkg'], - commit: true, - commitMessage: 'Release v%VERSION%', - commitFiles: ['package.json', 'CHANGELOG.md', 'dist/'], // '-a' for all files - createTag: true, - tagName: 'v%VERSION%', - tagMessage: 'Version %VERSION%', - push: false, - pushTo: 'origin', - gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe' - } - }, - - changelog: { - options: { - dest: 'CHANGELOG.md' - } - }, - - watch: { - files: ['libs/*.js', 'src/*.js', 'src/*.css', 'Gruntfile.js'], - tasks: ['concat', 'uglify', 'cssmin'] - }, - - karma: { - gridster: { - configFile: 'test/karma.conf.js' - } - } - }); + }); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); - grunt.loadNpmTasks('grunt-contrib-yuidoc'); - grunt.loadNpmTasks('grunt-bump'); - grunt.loadNpmTasks('grunt-conventional-changelog'); - grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-yuidoc'); + grunt.loadNpmTasks('grunt-bump'); + grunt.loadNpmTasks('grunt-conventional-changelog'); + grunt.loadNpmTasks('grunt-karma'); - // Default task. - grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'cssmin']); - grunt.registerTask('build', ['default']); - grunt.registerTask('docs', ['yuidoc']); + // Default task. + grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'cssmin']); + grunt.registerTask('build', ['default']); + grunt.registerTask('docs', ['yuidoc']); + grunt.registerTask('test', ['karma']); - grunt.registerTask('release', ['build', 'bump-only:patch', 'build', 'docs', 'changelog']); - grunt.registerTask('release:minor', ['build', 'bump-only:minor', 'build', 'docs', 'changelog']); - grunt.registerTask('release:major', ['build', 'bump-only:major', 'build', 'docs', 'changelog']); - grunt.registerTask('release:git', ['build', 'bump-only:git', 'build', 'docs', 'changelog', 'bump-commit']); - grunt.registerTask('release:commit', ['bump-commit']); + grunt.registerTask('release', [ + 'build', 'bump-only:patch', 'build', 'docs', 'changelog']); + grunt.registerTask('release:minor', [ + 'build', 'bump-only:minor', 'build', 'docs', 'changelog']); + grunt.registerTask('release:major', [ + 'build', 'bump-only:major', 'build', 'docs', 'changelog']); + grunt.registerTask('release:git', [ + 'build', 'bump-only:git', 'build', 'docs', 'changelog', 'bump-commit']); + grunt.registerTask('release:commit', [ + 'bump-commit']); }; diff --git a/package.json b/package.json index 5d1d10af6..66a764204 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,13 @@ "grunt-bump": "0.0.11", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-cssmin": "~0.5.0", - "grunt-contrib-jshint": "~0.3.0", + "grunt-contrib-jshint": "^0.11.2", "grunt-contrib-uglify": "~0.2.0", "grunt-contrib-watch": "~0.3.1", "grunt-contrib-yuidoc": "~0.4.0", "grunt-conventional-changelog": "~1.0.0", "grunt-karma": "^0.10.1", + "jshint-stylish": "^1.0.1", "karma": "^0.12.31", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.1.7", From 9863656b5c87c70231a94faf605cd473e4826fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20S=C3=A1nchez-Mar=C3=ADn?= Date: Thu, 16 Apr 2015 20:25:09 +0200 Subject: [PATCH 17/17] style(jshint): adapt code to pass jshint --- src/jquery.collision.js | 3 +++ src/jquery.coords.js | 4 ++++ src/jquery.draggable.js | 3 +++ src/jquery.gridster.extras.js | 11 +++++----- src/jquery.gridster.js | 41 +++++++++++++++-------------------- src/utils.js | 1 + 6 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/jquery.collision.js b/src/jquery.collision.js index a81d41406..798e465c8 100644 --- a/src/jquery.collision.js +++ b/src/jquery.collision.js @@ -7,6 +7,7 @@ */ ;(function(root, factory) { + // jshint strict: false if (typeof exports === 'object') { module.exports = factory(require('jquery'), require('./jquery.coords.js')); } else if (typeof define === 'function' && define.amd) { @@ -18,6 +19,8 @@ }(this, function($, Coords) { + 'use strict'; + var defaults = { colliders_context: document.body, overlapping_region: 'C' diff --git a/src/jquery.coords.js b/src/jquery.coords.js index ff7c0d68a..514d24a73 100644 --- a/src/jquery.coords.js +++ b/src/jquery.coords.js @@ -7,6 +7,7 @@ */ ;(function(root, factory) { + // jshint strict: false if (typeof exports === 'object') { module.exports = factory(require('jquery')); } else if (typeof define === 'function' && define.amd) { @@ -16,6 +17,9 @@ } }(this, function($) { + + 'use strict'; + /** * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height) * to simulate DOM elements on the screen. diff --git a/src/jquery.draggable.js b/src/jquery.draggable.js index d26369384..bdbd1b5b2 100644 --- a/src/jquery.draggable.js +++ b/src/jquery.draggable.js @@ -7,6 +7,7 @@ */ ;(function(root, factory) { + // jshint strict: false if (typeof exports === 'object') { module.exports = factory(require('jquery')); } else if (typeof define === 'function' && define.amd) { @@ -17,6 +18,8 @@ }(this, function($) { + 'use strict'; + var defaults = { items: 'li', distance: 1, diff --git a/src/jquery.gridster.extras.js b/src/jquery.gridster.extras.js index 6915a848b..45a298bc1 100644 --- a/src/jquery.gridster.extras.js +++ b/src/jquery.gridster.extras.js @@ -1,4 +1,5 @@ ;(function(root, factory) { + // jshint strict: false if (typeof exports === 'object') { module.exports = factory(require('jquery'), require('./jquery.gridster.js')); } else if (typeof define === 'function' && define.amd) { @@ -9,6 +10,8 @@ }(this, function($, Gridster) { + 'use strict'; + var fn = Gridster.prototype; fn.widgets_in_col = function(col) { @@ -37,8 +40,7 @@ fn.widgets_in_range = function(col1, row1, col2, row2) { - var valid_cols = []; - var valid_rows = []; + // jshint maxdepth:false var $widgets = $([]); var c, r, $w, wgd; @@ -49,8 +51,7 @@ if ($w !== false) { wgd = $w.data('coords').grid; if (wgd.col >= col1 && wgd.col <= col2 && - wgd.row >= row1 && wgd.row <= row2 - ) { + wgd.row >= row1 && wgd.row <= row2) { $widgets = $widgets.add($w); } } @@ -91,6 +92,7 @@ fn.for_each_cell = function(callback, gridmap) { + // jshint maxdepth:false gridmap || (gridmap = this.gridmap); var cols = gridmap.length; var rows = gridmap[1].length; @@ -159,7 +161,6 @@ fn.closest_to_left = function(col, row) { - var cols_l = this.gridmap.length - 1; if (!this.gridmap[col]) { return false; } for (var c = col; c >= 1; c--) { diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index e093b7d56..a1830bd5d 100755 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -7,6 +7,7 @@ */ ;(function(root, factory) { + // jshint strict: false if (typeof exports === 'object') { module.exports = factory(require('jquery'), require('./jquery.draggable.js'), require('./jquery.collision.js'), @@ -20,6 +21,8 @@ }(this, function($, Draggable, Collision) { + 'use strict'; + var defaults = { namespace: '', widget_selector: 'li', @@ -509,7 +512,6 @@ * @return {HTMLElement} Returns instance of gridster Class. */ fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) { - var old_size_x = wgd.size_x; var old_size_y = wgd.size_y; var old_cells_occupied = this.get_cells_occupied(wgd); @@ -546,14 +548,14 @@ this.remove_from_gridmap(wgd); if (occupied_cols.length) { - var cols_to_empty = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget - ]; + var cols_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, + Math.min(old_size_y, new_wgd.size_y), $widget]; this.empty_cells.apply(this, cols_to_empty); } if (occupied_rows.length) { - var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; + var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, + new_wgd.size_y, $widget]; this.empty_cells.apply(this, rows_to_empty); } @@ -630,7 +632,8 @@ $nexts.not($exclude).each($.proxy(function(i, w) { var wgd = $(w).coords().grid; - if ( !(wgd.row <= (row + size_y - 1))) { return; } + var full = wgd.row <= (row + size_y - 1); + if (!full) { return; } var diff = (row + size_y) - wgd.row; this.move_widget_down($(w), diff); }, this)); @@ -1351,6 +1354,8 @@ var width; var max_width = Infinity; var max_height = Infinity; + var min_width; + var min_height; var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); @@ -1987,8 +1992,10 @@ var r = min_row - 1; var valid_rows = []; + var common; while (++r <= p_bottom_row ) { - var common = true; + common = true; + // jshint loopfunc:true $.each(upper_rows, function(col, rows) { if ($.isArray(rows) && $.inArray(r, rows) === -1) { common = false; @@ -2051,7 +2058,6 @@ * @return {jQuery} Returns a jQuery collection of HTMLElements. */ fn.get_widgets_overlapped = function() { - var $w; var $widgets = $([]); var used = []; var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); @@ -2131,6 +2137,7 @@ var self = this; var cols = this.cells_occupied_by_player.cols; for (var c = 0, cl = cols.length; c < cl; c++) { + // jshint loopfunc:true this.for_each_widget_below(cols[c], row, function(tcol, trow) { self.move_widget_up(this, self.player_grid_data.size_y); }); @@ -2151,7 +2158,6 @@ fn.move_widget_to = function($widget, row) { var self = this; var widget_grid_data = $widget.coords().grid; - var diff = row - widget_grid_data.row; var $next_widgets = this.widgets_below($widget); var can_move_to_new_cell = this.can_move_to( @@ -2193,7 +2199,6 @@ var el_grid_data = $widget.coords().grid; var actual_row = el_grid_data.row; var moved = []; - var can_go_up = true; y_units || (y_units = 1); if (!this.can_go_up($widget)) { return false; } //break; @@ -2292,7 +2297,6 @@ * to the target position, else returns false. */ fn.can_go_up_to_row = function(widget_grid_data, col, row) { - var ga = this.gridmap; var result = true; var urc = []; // upper_rows_in_columns var actual_row = widget_grid_data.row; @@ -2301,7 +2305,6 @@ /* generate an array with columns as index and array with * upper rows empty in the column */ this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = ga[tcol]; urc[tcol] = []; r = actual_row; @@ -2380,7 +2383,6 @@ fn.widgets_below = function($el) { var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; var self = this; - var ga = this.gridmap; var next_row = el_grid_data.row + el_grid_data.size_y - 1; var $nexts = $([]); @@ -2430,15 +2432,11 @@ var el_grid_data = $el.coords().grid; var initial_row = el_grid_data.row; var prev_row = initial_row - 1; - var ga = this.gridmap; - var upper_rows_by_column = []; var result = true; if (initial_row === 1) { return false; } this.for_each_column_occupied(el_grid_data, function(col) { - var $w = this.is_widget(col, prev_row); - if (this.is_occupied(col, prev_row) || this.is_player(col, prev_row) || this.is_placeholder_in(col, prev_row) || @@ -2467,7 +2465,6 @@ * @return {Boolean} Returns true if all cells are empty, else return false. */ fn.can_move_to = function(widget_grid_data, col, row, max_row) { - var ga = this.gridmap; var $w = widget_grid_data.el; var future_wd = { size_y: widget_grid_data.size_y, @@ -2714,7 +2711,6 @@ var gm = this.gridmap; var rl = gm[1].length; var rows = [], cols = []; - var row_in_col = []; for (var c = gm.length - 1; c >= 1; c--) { for (r = rl - 1; r >= 1; r--) { if (this.is_widget(c, r)) { @@ -2733,7 +2729,6 @@ fn.get_widgets_from = function(col, row) { - var ga = this.gridmap; var $widgets = $(); if (col) { @@ -2807,10 +2802,7 @@ fn.generate_stylesheet = function(opts) { var styles = ''; var max_size_x = this.options.max_size_x || this.cols; - var max_rows = 0; - var max_cols = 0; var i; - var rules; opts || (opts = {}); opts.cols || (opts.cols = this.cols); @@ -3116,7 +3108,8 @@ }; /** - * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks + * Destroy the gridster instance by removing any sign of its presence, + * making it easy to avoid memory leaks * * @method destroy * @param {Boolean} remove If true, remove gridster from DOM. diff --git a/src/utils.js b/src/utils.js index f66910d78..7f8d0caa9 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,4 @@ +// jshint ignore:start ;(function(window, undefined) { /* Delay, debounce and throttle functions taken from underscore.js