380 lines
9.3 KiB
Plaintext
380 lines
9.3 KiB
Plaintext
/*
|
|
* 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('<div></div>');
|
|
|
|
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);
|