/* * jQuery Infinite Drag * Copyright (c) 2010 Ian Li (http://ianli.com) * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. * * Requires jQuery with either jQuery UI Draggable or jQuery Pep * * Reference: * http://ianli.com/infinitedrag/ for usage and examples * http://github.com/Sleavely/jquery-infinite-drag/ for additional documentation */ (function($) { /** * Function to create InfiniteDrag object. */ $.infinitedrag = function(draggable, draggable_options, tile_options) { return new InfiniteDrag(draggable, draggable_options, tile_options); }; $.infinitedrag.serializeTiles = function(tiles) { var pairs = []; $.each(tiles, function(key, tile) { pairs.push(tile.x + ';' + tile.y); }); return pairs.join(); }; /** * The InfiniteDrag object. */ var InfiniteDrag = function(draggable, draggable_options, tile_options) { // Use self to reduce confusion about this. var self = this; var $draggable = $(draggable); var $viewport = $draggable.parent(); $draggable.css({ position: "relative" }); // Draggable options var _do = { shouldEase: false, cursor: true }; $.extend(_do, draggable_options); if (_do.cursor) { $draggable.css({ cursor: "move" }); } // Tile options (DEFAULT) var _to = { class_name: "_tile", width: 100, height: 100, start_col: 0, start_row: 0, range_col: [-1000000, 1000000], range_row: [-1000000, 1000000], margin: 0, cleaning_enabled: true, remove_buffer: 10, draggable_lib: $.fn.pep ? "pep" : "draggable", oncreate: function($element, i, j) { $element.text(i + "," + j); }, on_aggregate: false, aggregate_time: 10 }; // Override tile options. $.extend(_to, tile_options); // Override tile options based on draggable options. if (_do.axis == "x") { _to.range_row = [_to.start_row, _to.start_row]; } else if (_do.axis == "y") { _to.range_col = [_to.start_col, _to.start_col]; } var aggregator_data = [], aggregator_timer = 0; // Creates the tile at (i, j). function create_tile(i, j) { if (i < _to.range_col[0] || _to.range_col[1] < i) { return; } else if (j < _to.range_row[0] || _to.range_row[1] < j) { return; } var x = i * _to.width; var y = j * _to.height; var $e = $draggable.append('
'); var $new_tile = $e.children(":last"); _storeTile($new_tile, i, j); $new_tile.attr({ "class": _to.class_name, col: i, row: j }); _setTileStyle($new_tile, i, j); if (_to.on_aggregate) { aggregator_data.push({ tile: $new_tile, x: i, y: j }); if (aggregator_timer === 0) { aggregator_timer = setTimeout(_fireAgregate, _to.aggregate_time); } } _to.oncreate.call(self, $new_tile, i, j); }; // Tries to register a tile function register_tile(elem) { var i = $(elem).attr('col'); var j = $(elem).attr('row'); if (typeof i === 'undefined' || typeof j === 'undefined') { return; } if (typeof grid[i] == "undefined") { grid[i] = {}; } _storeTile(elem, i, j); _setTileStyle(elem, i, j); } function _setTileStyle(tile, i, j) { var x = i * _to.width; var y = j * _to.height; $(tile).css({ position: "absolute", left: x, top: y, width: _to.width, height: _to.height }); } function _storeTile(tile, i, j) { grid[i][j] = $(tile).get(0); if (typeof grid[i].cnt == "undefined") grid[i].cnt = 0; grid[i].cnt++; } function _fireAgregate() { _to.on_aggregate.call(self, aggregator_data); aggregator_timer = 0; aggregator_data = []; } // Updates the containment box wherein the draggable can be dragged. self.update_containment = function() { // Update viewport info. viewport_zoom = $viewport.css('zoom'); viewport_width = $viewport.width() + _to.margin * 2; viewport_height = $viewport.height() + _to.margin * 2; viewport_cols = Math.ceil(viewport_width / _to.width); viewport_rows = Math.ceil(viewport_height / _to.height); // Create containment box. var half_width = _to.width / 2, half_height = _to.height / 2, viewport_offset = $viewport.offset(), viewport_draggable_width = (viewport_width / viewport_zoom) - _to.width, viewport_draggable_height = (viewport_height / viewport_zoom) - _to.height; var containment = [ (-_to.range_col[1] * _to.width) + viewport_offset.left + viewport_draggable_width, (-_to.range_row[1] * _to.height) + viewport_offset.top + viewport_draggable_height, (-_to.range_col[0] * _to.width) + viewport_offset.left, (-_to.range_row[0] * _to.height) + viewport_offset.top, ]; if (_to.draggable_lib == "draggable") { $draggable.draggable("option", "containment", containment); } // Force check for new tiles in visible area, // in case the containment was triggered by a zoom change. update_tiles(); }; var last_cleaned_tiles = { left: 0, top: 0 }; var update_tiles = function(dragged_pos) { if (typeof dragged_pos == "undefined") { dragged_pos = { left: 0, top: 0 } } var $this = $draggable; var $parent = $this.parent(); // Problem with .position() in Chrome/WebKit: // var pos = $(this).position(); // So, we compute it ourselves. var pos = { left: $this.offset().left - $parent.offset().left + _to.margin, top: $this.offset().top - $parent.offset().top + _to.margin } // - 1 because the previous tile is partially visible var visible_left_col = Math.ceil(-pos.left / _to.width) - 1, visible_top_row = Math.ceil(-pos.top / _to.height) - 1; for (var i = visible_left_col; i <= visible_left_col + viewport_cols; i++) { if (typeof grid[i] == "undefined") { grid[i] = {}; } for (var j = visible_top_row; j <= visible_top_row + viewport_rows; j++) { if (typeof grid[i][j] == "undefined") { create_tile(i, j); } } } if ( Math.abs(dragged_pos.left - last_cleaned_tiles.left) > (_to.remove_buffer * _to.width) || Math.abs(dragged_pos.top - last_cleaned_tiles.top) > (_to.remove_buffer * _to.height)) { remove_tiles(visible_left_col, visible_top_row); last_cleaned_tiles = dragged_pos; } }; // Removes unseen tiles //----------------------- var remove_tiles = function(left, top) { // Is cleaning disabled? if (_to.cleaning_enabled) { return; } // Finds tiles which can be seen based on window width & height var maxLeft = (left + viewport_cols) + 1, maxTop = (top + viewport_rows); $.each(grid, function(i, rows) { $.each(rows, function(j, elem) { if (j !== 'cnt') { if ((i < left) || (i > maxLeft) || (j < top) || (j > maxTop)) { delete grid[i][j]; grid[i].cnt--; if (grid[i].cnt == 0) delete grid[i]; $(elem).remove(); } } }); }); } // Public Methods //----------------- self.draggable = function() { return $draggable; }; self.disabled = function(value) { if (value === undefined) { return $draggable; } if ($.fn.pep) { $.pep.toggleAll(!value) } else { $draggable.draggable("option", "disabled", value); } if (_do.cursor) { $draggable.css({ cursor: (value) ? "default" : "move" }); } }; self.center = function(col, row) { var x = _to.width * col, y = _to.height * row, half_width = _to.width / 2, half_height = _to.height / 2, half_vw_width = $viewport.width() / 2, half_vw_height = $viewport.height() / 2, offset = $draggable.offset(); var new_offset = { left: -x - (half_width - half_vw_width), top: -y - (half_height - half_vw_height) }; if (_do.axis == "x") { new_offset.top = offset.top; } else if (_do.axis == "y") { new_offset.left = offset.left; } $draggable.offset(new_offset); update_tiles(new_offset); }; self.get_tile_dimensions = function() { var tileDims = { width: _to.width, height: _to.height }; return tileDims; }; self.get_tile = function(x, y) { if (typeof grid[x] !== 'undefined' && typeof grid[x][y] !== 'undefined') { return $(grid[x][y]); } return null; } // Setup //-------- //To make sure minimal setup will show something if ($viewport.height() == 0) { $viewport.css({ 'min-height': '300px' }); } var viewport_zoom = $viewport.css('zoom'), viewport_width = $viewport.width() + _to.margin * 2, viewport_height = $viewport.height() + _to.margin * 2, viewport_cols = Math.ceil(viewport_width / _to.width), viewport_rows = Math.ceil(viewport_height / _to.height); $draggable.offset({ left: $viewport.offset().left - (_to.start_col * _to.width), top: $viewport.offset().top - (_to.start_row * _to.height) }); var grid = {}; // Tries to register any existing tiles $draggable.children("." + _to.class_name).each(function() { register_tile(this); }); // Create initial tiles update_tiles(); // Handle resize of window. $(window).resize(function() { // HACK: // Update the containment when the window is resized // because the containment boundaries depend on the offset of the viewport. self.update_containment(); }); // The drag event handler. _do.drag = function(e, ui) { update_tiles(ui.position); }; $draggable[_to.draggable_lib](_do); self.update_containment(); }; })(jQuery);