Added jQuery.Shapeshift v2.0

This commit is contained in:
Scott Elwood
2013-04-24 16:07:39 -07:00
parent b8ff3804ce
commit 7ef53ffc5c
4 changed files with 1392 additions and 0 deletions
@@ -0,0 +1,724 @@
# Project: jQuery.Shapeshift
# Description: Align elements to grid with drag and drop.
# Author: Scott Elwood
# Maintained By: We the Media, inc.
# License: MIT
(($, window, document) ->
pluginName = "shapeshift"
defaults =
# The Basics
selector: "*"
# Features
enableDrag: true
enableCrossDrop: true
enableResize: true
enableTrash: false
# Grid Properties
align: "center"
colWidth: null
columns: null
minColumns: 1
autoHeight: true
maxHeight: null
minHeight: 100
gutterX: 10
gutterY: 10
paddingX: 10
paddingY: 10
# Animation
animated: true
animateOnInit: false
animationSpeed: 225
animationThreshold: 100
# Drag/Drop Options
dragClone: false
deleteClone: true
dragRate: 100
dragWhitelist: "*"
crossDropWhitelist: "*"
cutoffStart: null
cutoffEnd: null
handle: false
# Customize CSS
cloneClass: "ss-cloned-child"
activeClass: "ss-active-child"
draggedClass: "ss-dragged-child"
placeholderClass: "ss-placeholder-child"
originalContainerClass: "ss-original-container"
currentContainerClass: "ss-current-container"
previousContainerClass: "ss-previous-container"
class Plugin
constructor: (@element, options) ->
@options = $.extend {}, defaults, options
@globals = {}
@$container = $(element)
if @errorCheck()
@init()
# ----------------------------
# errorCheck:
# Determine if there are any conflicting options
# ----------------------------
errorCheck: ->
options = @options
errors = false
error_msg = "Shapeshift ERROR:"
# If there are no available children, a colWidth must be set
if options.colWidth is null
$children = @$container.children(options.selector)
if $children.length is 0
errors = true
console.error "#{error_msg} option colWidth must be specified if Shapeshift is initialized with no active children."
return !errors
# ----------------------------
# Init:
# Only enable features on initialization,
# then call a full render of the elements
# ----------------------------
init: ->
@createEvents()
@setGlobals()
@setIdentifier()
@setActiveChildren()
@enableFeatures()
@gridInit()
@render()
@afterInit()
# ----------------------------
# createEvents:
# Triggerable events on the container
# which run certain functions
# ----------------------------
createEvents: ->
options = @options
$container = @$container
$container.off("ss-arrange").on "ss-arrange", (e, trigger_drop_finished = false) => @render(false, trigger_drop_finished)
$container.off("ss-rearrange").on "ss-rearrange", => @render(true)
$container.off("ss-setTargetPosition").on "ss-setTargetPosition", => @setTargetPosition()
$container.off("ss-destroy").on "ss-destroy", => @destroy()
# ----------------------------
# setGlobals:
# Globals that only need to be set on initialization
# ----------------------------
setGlobals: ->
# Prevent initial animation if applicable
@globals.animated = @options.animateOnInit
# ----------------------------
# afterInit:
# Take care of some dirty business
# ----------------------------
afterInit: ->
# Return animation to normal
@globals.animated = @options.animated
# ----------------------------
# setIdentifier
# Create a random identifier to tie to this container so that
# it is easy to unbind the specific resize event from the browser
# ----------------------------
setIdentifier: ->
@identifier = "shapeshifted_container_" + Math.random().toString(36).substring(7)
@$container.addClass(@identifier)
# ----------------------------
# enableFeatures:
# Enables options features
# ----------------------------
enableFeatures: ->
@enableResize() if @options.enableResize
@enableDragNDrop() if @options.enableDrag or @options.enableCrossDrop
# ----------------------------
# setActiveChildren:
# Make sure that only the children set by the
# selector option can be affected by Shapeshifting
# ----------------------------
setActiveChildren: ->
options = @options
# Add active child class to each available child element
$children = @$container.children(options.selector)
active_child_class = options.activeClass
total = $children.length
for i in [0...total]
$($children[i]).addClass(active_child_class)
@setParsedChildren()
# Detect if there are any colspans wider than
# the column options that were set
columns = options.columns
for i in [0...@parsedChildren.length]
colspan = @parsedChildren[i].colspan
min_columns = options.minColumns
if colspan > columns and colspan > min_columns
options.minColumns = colspan
console.error "Shapeshift ERROR: There are child elements that have a larger colspan than the minimum columns set through options.\noptions.minColumns has been set to #{colspan}"
# ----------------------------
# setParsedChildren:
# Calculates and returns commonly used
# attributes for all the active children
# ----------------------------
setParsedChildren: ->
$children = @$container.find("." + @options.activeClass).filter(":visible")
total = $children.length
parsedChildren = []
for i in [0...total]
$child = $($children[i])
child =
i: i
el: $child
colspan: parseInt($child.attr("data-ss-colspan")) || 1
height: $child.outerHeight()
parsedChildren.push child
@parsedChildren = parsedChildren
# ----------------------------
# setGrid:
# Calculates the dimensions of each column
# and determines to total number of columns
# ----------------------------
gridInit: ->
gutter_x = @options.gutterX
unless @options.colWidth >= 1
# Determine single item / col width
first_child = @parsedChildren[0]
fc_width = first_child.el.outerWidth()
fc_colspan = first_child.colspan
single_width = (fc_width - ((fc_colspan - 1) * gutter_x)) / fc_colspan
@globals.col_width = single_width + gutter_x
else
@globals.col_width = @options.colWidth + gutter_x
# ----------------------------
# render:
# Determine the active children and
# arrange them to the calculated grid
# ----------------------------
render: (reparse = false, trigger_drop_finished) ->
@setGridColumns()
@arrange(reparse, trigger_drop_finished)
# ----------------------------
# setGrid:
# Calculates the dimensions of each column
# and determines to total number of columns
# ----------------------------
setGridColumns: ->
# Common
globals = @globals
options = @options
col_width = globals.col_width
gutter_x = options.gutterX
padding_x = options.paddingX
inner_width = @$container.innerWidth() - (padding_x * 2)
# Determine how many columns there currently can be
minColumns = options.minColumns
columns = options.columns || Math.floor (inner_width + gutter_x) / col_width
if minColumns and minColumns > columns
columns = minColumns
globals.columns = columns
# Columns cannot exceed children
children_count = @parsedChildren.length
if columns > children_count
columns = children_count
# Calculate the child offset from the left
globals.child_offset = padding_x
switch options.align
when "center"
grid_width = (columns * col_width) - gutter_x
globals.child_offset += (inner_width - grid_width) / 2
when "right"
grid_width = (columns * col_width) - gutter_x
globals.child_offset += (inner_width - grid_width)
# ----------------------------
# arrange:
# Animates the elements into their calcluated positions
# ----------------------------
arrange: (reparse, trigger_drop_finished) ->
@setParsedChildren() if reparse
globals = @globals
options = @options
# Common
$container = @$container
child_positions = @getPositions()
parsed_children = @parsedChildren
total_children = parsed_children.length
animated = globals.animated and total_children <= options.animationThreshold
animation_speed = options.animationSpeed
dragged_class = options.draggedClass
# Arrange each child element
for i in [0...total_children]
$child = parsed_children[i].el
attributes = child_positions[i]
is_dragged_child = $child.hasClass(dragged_class)
if is_dragged_child
placeholder_class = options.placeholderClass
$child = $child.siblings("." + placeholder_class)
if animated and !is_dragged_child
$child.stop(true, false).animate attributes, animation_speed, ->
else
$child.css attributes
if trigger_drop_finished
if animated
setTimeout (->
$container.trigger("ss-drop-complete")
), animation_speed
else
$container.trigger("ss-drop-complete")
$container.trigger("ss-arranged")
# Set the container height
if options.autoHeight
container_height = globals.container_height
max_height = options.maxHeight
min_height = options.minHeight
if min_height and container_height < min_height
container_height = min_height
else if max_height and container_height > max_height
container_height = max_height
$container.height container_height
# ----------------------------
# getPositions:
# Go over each child and determine which column they
# fit into and return an array of their x/y dimensions
# ----------------------------
getPositions: (include_dragged = true) ->
globals = @globals
options = @options
gutter_y = options.gutterY
padding_y = options.paddingY
dragged_class = options.draggedClass
parsed_children = @parsedChildren
total_children = parsed_children.length
# Store the height for each column
col_heights = []
for i in [0...globals.columns]
col_heights.push padding_y
# ----------------------------
# savePosition
# Takes a child which has been correctly placed in a
# column and saves it to that final x/y position.
# ----------------------------
savePosition = (child) =>
col = child.col
colspan = child.colspan
offset_x = (child.col * globals.col_width) + globals.child_offset
offset_y = col_heights[col]
positions[child.i] = left: offset_x, top: offset_y
col_heights[col] += child.height + gutter_y
if colspan >= 1
for j in [1...colspan]
col_heights[col + j] = col_heights[col]
# ----------------------------
# determineMultiposition
# Children with multiple column spans will need special
# rules to determine if they are currently able to be
# placed in the grid.
# ----------------------------
determineMultiposition = (child) =>
# Only use the columns that this child can fit into
possible_cols = col_heights.length - child.colspan + 1
possible_col_heights = col_heights.slice(0).splice(0, possible_cols)
chosen_col = undefined
for offset in [0...possible_cols]
col = @lowestCol(possible_col_heights, offset)
colspan = child.colspan
height = col_heights[col]
kosher = true
# Determine if it is able to be placed at this col
for span in [1...colspan]
next_height = col_heights[col + span]
# The next height must not be higher
if height < next_height
kosher = false
break
if kosher
chosen_col = col
break
return chosen_col
# ----------------------------
# recalculateSavedChildren
# Sometimes child elements cannot save the first time around,
# iterate over those children and determine if its ok to place now.
# ----------------------------
saved_children = []
recalculateSavedChildren = =>
to_pop = []
for saved_i in [0...saved_children.length]
saved_child = saved_children[saved_i]
saved_child.col = determineMultiposition(saved_child)
if saved_child.col >= 0
savePosition(saved_child)
to_pop.push(saved_i)
# Popeye. Lol.
for pop_i in [to_pop.length - 1..0] by -1
index = to_pop[pop_i]
saved_children.splice(index,1)
# ----------------------------
# determinePositions
# Iterate over all the parsed children and determine
# the calculations needed to get its x/y value.
# ----------------------------
positions = []
do determinePositions = =>
for i in [0...total_children]
child = parsed_children[i]
unless !include_dragged and child.el.hasClass(dragged_class)
if child.colspan > 1
child.col = determineMultiposition(child)
else
child.col = @lowestCol(col_heights)
if child.col is undefined
saved_children.push child
else
savePosition(child)
recalculateSavedChildren()
# Store the container height since we already have the data
if options.autoHeight
grid_height = col_heights[@highestCol(col_heights)] - gutter_y
globals.container_height = grid_height + padding_y
return positions
# ----------------------------
# enableDrag:
# Optional feature.
# Initialize dragging.
# ----------------------------
enableDragNDrop: ->
options = @options
$container = @$container
active_class = options.activeClass
dragged_class = options.draggedClass
placeholder_class = options.placeholderClass
original_container_class = options.originalContainerClass
current_container_class = options.currentContainerClass
previous_container_class = options.previousContainerClass
delete_clone = options.deleteClone
drag_rate = options.dragRate
drag_clone = options.dragClone
clone_class = options.cloneClass
$selected = $placeholder = $clone = selected_offset_y = selected_offset_x = null
dragging = false
if options.enableDrag
$container.children("." + active_class).filter(options.dragWhitelist).draggable
addClasses: false
containment: 'document'
handle: options.handle
zIndex: 9999
start: (e, ui) ->
# Set $selected globals
$selected = $(e.target)
if drag_clone
$clone = $selected.clone(true).insertBefore($selected).addClass(clone_class)
$selected.addClass(dragged_class)
# Create Placeholder
selected_tag = $selected.prop("tagName")
$placeholder = $("<#{selected_tag} class='#{placeholder_class}' style='height: #{$selected.height()}px; width: #{$selected.width()}px'></#{selected_tag}>")
# Set current container
$selected.parent().addClass(original_container_class).addClass(current_container_class)
# For manually centering the element with respect to mouse position
selected_offset_y = $selected.outerHeight() / 2
selected_offset_x = $selected.outerWidth() / 2
drag: (e, ui) =>
if !dragging and !(drag_clone and delete_clone and $("." + current_container_class)[0] is $("." + original_container_class)[0])
# Append placeholder to container
$placeholder.remove().appendTo("." + current_container_class)
# Set drag target and rearrange everything
$("." + current_container_class).trigger("ss-setTargetPosition")
# Disallow dragging from occurring too much
dragging = true
window.setTimeout ( ->
dragging = false
), drag_rate
# Manually center the element with respect to mouse position
ui.position.left = e.pageX - $selected.parent().offset().left - selected_offset_x;
ui.position.top = e.pageY - $selected.parent().offset().top - selected_offset_y;
stop: ->
$original_container = $("." + original_container_class)
$current_container = $("." + current_container_class)
$previous_container = $("." + previous_container_class)
# Clear globals
$selected.removeClass(dragged_class)
$("." + placeholder_class).remove()
if drag_clone
if delete_clone and $("." + current_container_class)[0] is $("." + original_container_class)[0]
$clone.remove()
$("." + current_container_class).trigger("ss-rearrange")
else
$clone.removeClass(clone_class)
# Trigger Events
if $original_container[0] is $current_container[0]
$current_container.trigger("ss-rearranged", $selected)
else
$original_container.trigger("ss-removed", $selected)
$current_container.trigger("ss-added", $selected)
# Arrange dragged item into place and clear container classes
$original_container.trigger("ss-arrange").removeClass(original_container_class)
$current_container.trigger("ss-arrange", true).removeClass(current_container_class)
$previous_container.trigger("ss-arrange").removeClass(previous_container_class)
$selected = $placeholder = null
if options.enableCrossDrop
$container.droppable
accept: options.crossDropWhitelist
tolerance: 'intersect'
over: (e) =>
$("." + previous_container_class).removeClass(previous_container_class)
$("." + current_container_class).removeClass(current_container_class).addClass(previous_container_class)
$(e.target).addClass(current_container_class)
drop: (e, selected) =>
if @options.enableTrash
$original_container = $("." + original_container_class)
$current_container = $("." + current_container_class)
$previous_container = $("." + previous_container_class)
$selected = $(selected.helper)
$current_container.trigger("ss-trashed", $selected)
$selected.remove()
$original_container.trigger("ss-rearrange").removeClass(original_container_class)
$current_container.trigger("ss-rearrange").removeClass(current_container_class)
$previous_container.trigger("ss-arrange").removeClass(previous_container_class)
# ----------------------------
# getTargetPosition:
# Determine the target position for the selected
# element and arrange it into place
# ----------------------------
setTargetPosition: ->
options = @options
unless options.enableTrash
dragged_class = options.draggedClass
$selected = $("." + dragged_class)
$start_container = $selected.parent()
parsed_children = @parsedChildren
child_positions = @getPositions(false)
total_positions = child_positions.length
selected_x = $selected.offset().left - $start_container.offset().left + (@globals.col_width / 2)
selected_y = $selected.offset().top - $start_container.offset().top + ($selected.height() / 2)
shortest_distance = 9999999
target_position = 0
if total_positions > 1
cutoff_start = options.cutoffStart + 1 || 0
cutoff_end = options.cutoffEnd || total_positions
for position_i in [cutoff_start...cutoff_end]
attributes = child_positions[position_i]
if attributes
y_dist = selected_x - attributes.left
x_dist = selected_y - attributes.top
if y_dist > 0 and x_dist > 0
distance = Math.sqrt((x_dist * x_dist) + (y_dist * y_dist))
if distance < shortest_distance
shortest_distance = distance
target_position = position_i
if position_i is total_positions - 1
if y_dist > parsed_children[position_i].height / 2
target_position++
if target_position is parsed_children.length
$target = parsed_children[target_position - 1].el
$selected.insertAfter($target)
else
$target = parsed_children[target_position].el
$selected.insertBefore($target)
else
if total_positions is 1
attributes = child_positions[0]
if attributes.left < selected_x
@$container.append $selected
else
@$container.prepend $selected
else
@$container.append $selected
@arrange(true)
if $start_container[0] isnt $selected.parent()[0]
previous_container_class = options.previousContainerClass
$("." + previous_container_class).trigger "ss-rearrange"
else
placeholder_class = @options.placeholderClass
$("." + placeholder_class).remove()
# ----------------------------
# resize:
# Optional feature.
# Runs a full render of the elements when
# the browser window is resized.
# ----------------------------
enableResize: ->
animation_speed = @options.animationSpeed
resizing = false
binding = "resize." + @identifier
$(window).on binding, =>
unless resizing
resizing = true
# Some funkyness to prevent too many renderings
setTimeout (=> @render()), animation_speed / 3
setTimeout (=> @render()), animation_speed / 3
setTimeout =>
resizing = false
@render()
, animation_speed / 3
# ----------------------------
# lowestCol:
# Helper
# Returns the index position of the
# array column with the lowest number
# ----------------------------
lowestCol: (array, offset = 0) ->
length = array.length
augmented_array = []
for i in [0...length]
augmented_array.push [array[i], i]
augmented_array.sort (a, b) ->
ret = a[0] - b[0]
ret = a[1] - b[1] if ret is 0
ret
augmented_array[offset][1]
# ----------------------------
# highestCol:
# Helper
# Returns the index position of the
# array column with the highest number
# ----------------------------
highestCol: (array) ->
$.inArray Math.max.apply(window,array), array
# ----------------------------
# destroy:
# ----------------------------
destroy: ->
$container = @$container
$container.off("ss-arrange")
$container.off("ss-rearrange")
$container.off("ss-setTargetPosition")
$container.off("ss-destroy")
active_class = @options.activeClass
$active_children = $container.find("." + active_class)
if @options.enableDrag
$active_children.draggable('destroy')
if @options.enableCrossDrop
$container.droppable('destroy')
$active_children.removeClass(active_class)
$container.removeClass(@identifier)
$.fn[pluginName] = (options) ->
@each ->
# Destroy any old resize events
old_class = $(@).attr("class").match(/shapeshifted_container_\w+/)?[0]
if old_class
bound_indentifier = "resize." + old_class
$(window).off(bound_indentifier)
$(@).removeClass(old_class)
# Create the new plugin instance
$.data(@, "plugin_#{pluginName}", new Plugin(@, options))
)(jQuery, window, document)
@@ -0,0 +1,634 @@
// Generated by CoffeeScript 1.4.0
(function() {
(function($, window, document) {
var Plugin, defaults, pluginName;
pluginName = "shapeshift";
defaults = {
selector: "*",
enableDrag: true,
enableCrossDrop: true,
enableResize: true,
enableTrash: false,
align: "center",
colWidth: null,
columns: null,
minColumns: 1,
autoHeight: true,
maxHeight: null,
minHeight: 100,
gutterX: 10,
gutterY: 10,
paddingX: 10,
paddingY: 10,
animated: true,
animateOnInit: false,
animationSpeed: 225,
animationThreshold: 100,
dragClone: false,
deleteClone: true,
dragRate: 100,
dragWhitelist: "*",
crossDropWhitelist: "*",
cutoffStart: null,
cutoffEnd: null,
handle: false,
cloneClass: "ss-cloned-child",
activeClass: "ss-active-child",
draggedClass: "ss-dragged-child",
placeholderClass: "ss-placeholder-child",
originalContainerClass: "ss-original-container",
currentContainerClass: "ss-current-container",
previousContainerClass: "ss-previous-container"
};
Plugin = (function() {
function Plugin(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this.globals = {};
this.$container = $(element);
if (this.errorCheck()) {
this.init();
}
}
Plugin.prototype.errorCheck = function() {
var $children, error_msg, errors, options;
options = this.options;
errors = false;
error_msg = "Shapeshift ERROR:";
if (options.colWidth === null) {
$children = this.$container.children(options.selector);
if ($children.length === 0) {
errors = true;
console.error("" + error_msg + " option colWidth must be specified if Shapeshift is initialized with no active children.");
}
}
return !errors;
};
Plugin.prototype.init = function() {
this.createEvents();
this.setGlobals();
this.setIdentifier();
this.setActiveChildren();
this.enableFeatures();
this.gridInit();
this.render();
return this.afterInit();
};
Plugin.prototype.createEvents = function() {
var $container, options,
_this = this;
options = this.options;
$container = this.$container;
$container.off("ss-arrange").on("ss-arrange", function(e, trigger_drop_finished) {
if (trigger_drop_finished == null) {
trigger_drop_finished = false;
}
return _this.render(false, trigger_drop_finished);
});
$container.off("ss-rearrange").on("ss-rearrange", function() {
return _this.render(true);
});
$container.off("ss-setTargetPosition").on("ss-setTargetPosition", function() {
return _this.setTargetPosition();
});
return $container.off("ss-destroy").on("ss-destroy", function() {
return _this.destroy();
});
};
Plugin.prototype.setGlobals = function() {
return this.globals.animated = this.options.animateOnInit;
};
Plugin.prototype.afterInit = function() {
return this.globals.animated = this.options.animated;
};
Plugin.prototype.setIdentifier = function() {
this.identifier = "shapeshifted_container_" + Math.random().toString(36).substring(7);
return this.$container.addClass(this.identifier);
};
Plugin.prototype.enableFeatures = function() {
if (this.options.enableResize) {
this.enableResize();
}
if (this.options.enableDrag || this.options.enableCrossDrop) {
return this.enableDragNDrop();
}
};
Plugin.prototype.setActiveChildren = function() {
var $children, active_child_class, colspan, columns, i, min_columns, options, total, _i, _j, _ref, _results;
options = this.options;
$children = this.$container.children(options.selector);
active_child_class = options.activeClass;
total = $children.length;
for (i = _i = 0; 0 <= total ? _i < total : _i > total; i = 0 <= total ? ++_i : --_i) {
$($children[i]).addClass(active_child_class);
}
this.setParsedChildren();
columns = options.columns;
_results = [];
for (i = _j = 0, _ref = this.parsedChildren.length; 0 <= _ref ? _j < _ref : _j > _ref; i = 0 <= _ref ? ++_j : --_j) {
colspan = this.parsedChildren[i].colspan;
min_columns = options.minColumns;
if (colspan > columns && colspan > min_columns) {
options.minColumns = colspan;
_results.push(console.error("Shapeshift ERROR: There are child elements that have a larger colspan than the minimum columns set through options.\noptions.minColumns has been set to " + colspan));
} else {
_results.push(void 0);
}
}
return _results;
};
Plugin.prototype.setParsedChildren = function() {
var $child, $children, child, i, parsedChildren, total, _i;
$children = this.$container.find("." + this.options.activeClass).filter(":visible");
total = $children.length;
parsedChildren = [];
for (i = _i = 0; 0 <= total ? _i < total : _i > total; i = 0 <= total ? ++_i : --_i) {
$child = $($children[i]);
child = {
i: i,
el: $child,
colspan: parseInt($child.attr("data-ss-colspan")) || 1,
height: $child.outerHeight()
};
parsedChildren.push(child);
}
return this.parsedChildren = parsedChildren;
};
Plugin.prototype.gridInit = function() {
var fc_colspan, fc_width, first_child, gutter_x, single_width;
gutter_x = this.options.gutterX;
if (!(this.options.colWidth >= 1)) {
first_child = this.parsedChildren[0];
fc_width = first_child.el.outerWidth();
fc_colspan = first_child.colspan;
single_width = (fc_width - ((fc_colspan - 1) * gutter_x)) / fc_colspan;
return this.globals.col_width = single_width + gutter_x;
} else {
return this.globals.col_width = this.options.colWidth + gutter_x;
}
};
Plugin.prototype.render = function(reparse, trigger_drop_finished) {
if (reparse == null) {
reparse = false;
}
this.setGridColumns();
return this.arrange(reparse, trigger_drop_finished);
};
Plugin.prototype.setGridColumns = function() {
var children_count, col_width, columns, globals, grid_width, gutter_x, inner_width, minColumns, options, padding_x;
globals = this.globals;
options = this.options;
col_width = globals.col_width;
gutter_x = options.gutterX;
padding_x = options.paddingX;
inner_width = this.$container.innerWidth() - (padding_x * 2);
minColumns = options.minColumns;
columns = options.columns || Math.floor((inner_width + gutter_x) / col_width);
if (minColumns && minColumns > columns) {
columns = minColumns;
}
globals.columns = columns;
children_count = this.parsedChildren.length;
if (columns > children_count) {
columns = children_count;
}
globals.child_offset = padding_x;
switch (options.align) {
case "center":
grid_width = (columns * col_width) - gutter_x;
return globals.child_offset += (inner_width - grid_width) / 2;
case "right":
grid_width = (columns * col_width) - gutter_x;
return globals.child_offset += inner_width - grid_width;
}
};
Plugin.prototype.arrange = function(reparse, trigger_drop_finished) {
var $child, $container, animated, animation_speed, attributes, child_positions, container_height, dragged_class, globals, i, is_dragged_child, max_height, min_height, options, parsed_children, placeholder_class, total_children, _i;
if (reparse) {
this.setParsedChildren();
}
globals = this.globals;
options = this.options;
$container = this.$container;
child_positions = this.getPositions();
parsed_children = this.parsedChildren;
total_children = parsed_children.length;
animated = globals.animated && total_children <= options.animationThreshold;
animation_speed = options.animationSpeed;
dragged_class = options.draggedClass;
for (i = _i = 0; 0 <= total_children ? _i < total_children : _i > total_children; i = 0 <= total_children ? ++_i : --_i) {
$child = parsed_children[i].el;
attributes = child_positions[i];
is_dragged_child = $child.hasClass(dragged_class);
if (is_dragged_child) {
placeholder_class = options.placeholderClass;
$child = $child.siblings("." + placeholder_class);
}
if (animated && !is_dragged_child) {
$child.stop(true, false).animate(attributes, animation_speed, function() {});
} else {
$child.css(attributes);
}
}
if (trigger_drop_finished) {
if (animated) {
setTimeout((function() {
return $container.trigger("ss-drop-complete");
}), animation_speed);
} else {
$container.trigger("ss-drop-complete");
}
}
$container.trigger("ss-arranged");
if (options.autoHeight) {
container_height = globals.container_height;
max_height = options.maxHeight;
min_height = options.minHeight;
if (min_height && container_height < min_height) {
container_height = min_height;
} else if (max_height && container_height > max_height) {
container_height = max_height;
}
return $container.height(container_height);
}
};
Plugin.prototype.getPositions = function(include_dragged) {
var col_heights, determineMultiposition, determinePositions, dragged_class, globals, grid_height, gutter_y, i, options, padding_y, parsed_children, positions, recalculateSavedChildren, savePosition, saved_children, total_children, _i, _ref,
_this = this;
if (include_dragged == null) {
include_dragged = true;
}
globals = this.globals;
options = this.options;
gutter_y = options.gutterY;
padding_y = options.paddingY;
dragged_class = options.draggedClass;
parsed_children = this.parsedChildren;
total_children = parsed_children.length;
col_heights = [];
for (i = _i = 0, _ref = globals.columns; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
col_heights.push(padding_y);
}
savePosition = function(child) {
var col, colspan, j, offset_x, offset_y, _j, _results;
col = child.col;
colspan = child.colspan;
offset_x = (child.col * globals.col_width) + globals.child_offset;
offset_y = col_heights[col];
positions[child.i] = {
left: offset_x,
top: offset_y
};
col_heights[col] += child.height + gutter_y;
if (colspan >= 1) {
_results = [];
for (j = _j = 1; 1 <= colspan ? _j < colspan : _j > colspan; j = 1 <= colspan ? ++_j : --_j) {
_results.push(col_heights[col + j] = col_heights[col]);
}
return _results;
}
};
determineMultiposition = function(child) {
var chosen_col, col, colspan, height, kosher, next_height, offset, possible_col_heights, possible_cols, span, _j, _k;
possible_cols = col_heights.length - child.colspan + 1;
possible_col_heights = col_heights.slice(0).splice(0, possible_cols);
chosen_col = void 0;
for (offset = _j = 0; 0 <= possible_cols ? _j < possible_cols : _j > possible_cols; offset = 0 <= possible_cols ? ++_j : --_j) {
col = _this.lowestCol(possible_col_heights, offset);
colspan = child.colspan;
height = col_heights[col];
kosher = true;
for (span = _k = 1; 1 <= colspan ? _k < colspan : _k > colspan; span = 1 <= colspan ? ++_k : --_k) {
next_height = col_heights[col + span];
if (height < next_height) {
kosher = false;
break;
}
}
if (kosher) {
chosen_col = col;
break;
}
}
return chosen_col;
};
saved_children = [];
recalculateSavedChildren = function() {
var index, pop_i, saved_child, saved_i, to_pop, _j, _k, _ref1, _ref2, _results;
to_pop = [];
for (saved_i = _j = 0, _ref1 = saved_children.length; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; saved_i = 0 <= _ref1 ? ++_j : --_j) {
saved_child = saved_children[saved_i];
saved_child.col = determineMultiposition(saved_child);
if (saved_child.col >= 0) {
savePosition(saved_child);
to_pop.push(saved_i);
}
}
_results = [];
for (pop_i = _k = _ref2 = to_pop.length - 1; _k >= 0; pop_i = _k += -1) {
index = to_pop[pop_i];
_results.push(saved_children.splice(index, 1));
}
return _results;
};
positions = [];
(determinePositions = function() {
var child, _j, _results;
_results = [];
for (i = _j = 0; 0 <= total_children ? _j < total_children : _j > total_children; i = 0 <= total_children ? ++_j : --_j) {
child = parsed_children[i];
if (!(!include_dragged && child.el.hasClass(dragged_class))) {
if (child.colspan > 1) {
child.col = determineMultiposition(child);
} else {
child.col = _this.lowestCol(col_heights);
}
if (child.col === void 0) {
saved_children.push(child);
} else {
savePosition(child);
}
_results.push(recalculateSavedChildren());
} else {
_results.push(void 0);
}
}
return _results;
})();
if (options.autoHeight) {
grid_height = col_heights[this.highestCol(col_heights)] - gutter_y;
globals.container_height = grid_height + padding_y;
}
return positions;
};
Plugin.prototype.enableDragNDrop = function() {
var $clone, $container, $placeholder, $selected, active_class, clone_class, current_container_class, delete_clone, drag_clone, drag_rate, dragged_class, dragging, options, original_container_class, placeholder_class, previous_container_class, selected_offset_x, selected_offset_y,
_this = this;
options = this.options;
$container = this.$container;
active_class = options.activeClass;
dragged_class = options.draggedClass;
placeholder_class = options.placeholderClass;
original_container_class = options.originalContainerClass;
current_container_class = options.currentContainerClass;
previous_container_class = options.previousContainerClass;
delete_clone = options.deleteClone;
drag_rate = options.dragRate;
drag_clone = options.dragClone;
clone_class = options.cloneClass;
$selected = $placeholder = $clone = selected_offset_y = selected_offset_x = null;
dragging = false;
if (options.enableDrag) {
$container.children("." + active_class).filter(options.dragWhitelist).draggable({
addClasses: false,
containment: 'document',
handle: options.handle,
zIndex: 9999,
start: function(e, ui) {
var selected_tag;
$selected = $(e.target);
if (drag_clone) {
$clone = $selected.clone(true).insertBefore($selected).addClass(clone_class);
}
$selected.addClass(dragged_class);
selected_tag = $selected.prop("tagName");
$placeholder = $("<" + selected_tag + " class='" + placeholder_class + "' style='height: " + ($selected.height()) + "px; width: " + ($selected.width()) + "px'></" + selected_tag + ">");
$selected.parent().addClass(original_container_class).addClass(current_container_class);
selected_offset_y = $selected.outerHeight() / 2;
return selected_offset_x = $selected.outerWidth() / 2;
},
drag: function(e, ui) {
if (!dragging && !(drag_clone && delete_clone && $("." + current_container_class)[0] === $("." + original_container_class)[0])) {
$placeholder.remove().appendTo("." + current_container_class);
$("." + current_container_class).trigger("ss-setTargetPosition");
dragging = true;
window.setTimeout((function() {
return dragging = false;
}), drag_rate);
}
ui.position.left = e.pageX - $selected.parent().offset().left - selected_offset_x;
return ui.position.top = e.pageY - $selected.parent().offset().top - selected_offset_y;
},
stop: function() {
var $current_container, $original_container, $previous_container;
$original_container = $("." + original_container_class);
$current_container = $("." + current_container_class);
$previous_container = $("." + previous_container_class);
$selected.removeClass(dragged_class);
$("." + placeholder_class).remove();
if (drag_clone) {
if (delete_clone && $("." + current_container_class)[0] === $("." + original_container_class)[0]) {
$clone.remove();
$("." + current_container_class).trigger("ss-rearrange");
} else {
$clone.removeClass(clone_class);
}
}
if ($original_container[0] === $current_container[0]) {
$current_container.trigger("ss-rearranged", $selected);
} else {
$original_container.trigger("ss-removed", $selected);
$current_container.trigger("ss-added", $selected);
}
$original_container.trigger("ss-arrange").removeClass(original_container_class);
$current_container.trigger("ss-arrange", true).removeClass(current_container_class);
$previous_container.trigger("ss-arrange").removeClass(previous_container_class);
return $selected = $placeholder = null;
}
});
}
if (options.enableCrossDrop) {
return $container.droppable({
accept: options.crossDropWhitelist,
tolerance: 'intersect',
over: function(e) {
$("." + previous_container_class).removeClass(previous_container_class);
$("." + current_container_class).removeClass(current_container_class).addClass(previous_container_class);
return $(e.target).addClass(current_container_class);
},
drop: function(e, selected) {
var $current_container, $original_container, $previous_container;
if (_this.options.enableTrash) {
$original_container = $("." + original_container_class);
$current_container = $("." + current_container_class);
$previous_container = $("." + previous_container_class);
$selected = $(selected.helper);
$current_container.trigger("ss-trashed", $selected);
$selected.remove();
$original_container.trigger("ss-rearrange").removeClass(original_container_class);
$current_container.trigger("ss-rearrange").removeClass(current_container_class);
return $previous_container.trigger("ss-arrange").removeClass(previous_container_class);
}
}
});
}
};
Plugin.prototype.setTargetPosition = function() {
var $selected, $start_container, $target, attributes, child_positions, cutoff_end, cutoff_start, distance, dragged_class, options, parsed_children, placeholder_class, position_i, previous_container_class, selected_x, selected_y, shortest_distance, target_position, total_positions, x_dist, y_dist, _i;
options = this.options;
if (!options.enableTrash) {
dragged_class = options.draggedClass;
$selected = $("." + dragged_class);
$start_container = $selected.parent();
parsed_children = this.parsedChildren;
child_positions = this.getPositions(false);
total_positions = child_positions.length;
selected_x = $selected.offset().left - $start_container.offset().left + (this.globals.col_width / 2);
selected_y = $selected.offset().top - $start_container.offset().top + ($selected.height() / 2);
shortest_distance = 9999999;
target_position = 0;
if (total_positions > 1) {
cutoff_start = options.cutoffStart + 1 || 0;
cutoff_end = options.cutoffEnd || total_positions;
for (position_i = _i = cutoff_start; cutoff_start <= cutoff_end ? _i < cutoff_end : _i > cutoff_end; position_i = cutoff_start <= cutoff_end ? ++_i : --_i) {
attributes = child_positions[position_i];
if (attributes) {
y_dist = selected_x - attributes.left;
x_dist = selected_y - attributes.top;
if (y_dist > 0 && x_dist > 0) {
distance = Math.sqrt((x_dist * x_dist) + (y_dist * y_dist));
if (distance < shortest_distance) {
shortest_distance = distance;
target_position = position_i;
if (position_i === total_positions - 1) {
if (y_dist > parsed_children[position_i].height / 2) {
target_position++;
}
}
}
}
}
}
if (target_position === parsed_children.length) {
$target = parsed_children[target_position - 1].el;
$selected.insertAfter($target);
} else {
$target = parsed_children[target_position].el;
$selected.insertBefore($target);
}
} else {
if (total_positions === 1) {
attributes = child_positions[0];
if (attributes.left < selected_x) {
this.$container.append($selected);
} else {
this.$container.prepend($selected);
}
} else {
this.$container.append($selected);
}
}
this.arrange(true);
if ($start_container[0] !== $selected.parent()[0]) {
previous_container_class = options.previousContainerClass;
return $("." + previous_container_class).trigger("ss-rearrange");
}
} else {
placeholder_class = this.options.placeholderClass;
return $("." + placeholder_class).remove();
}
};
Plugin.prototype.enableResize = function() {
var animation_speed, binding, resizing,
_this = this;
animation_speed = this.options.animationSpeed;
resizing = false;
binding = "resize." + this.identifier;
return $(window).on(binding, function() {
if (!resizing) {
resizing = true;
setTimeout((function() {
return _this.render();
}), animation_speed / 3);
setTimeout((function() {
return _this.render();
}), animation_speed / 3);
return setTimeout(function() {
resizing = false;
return _this.render();
}, animation_speed / 3);
}
});
};
Plugin.prototype.lowestCol = function(array, offset) {
var augmented_array, i, length, _i;
if (offset == null) {
offset = 0;
}
length = array.length;
augmented_array = [];
for (i = _i = 0; 0 <= length ? _i < length : _i > length; i = 0 <= length ? ++_i : --_i) {
augmented_array.push([array[i], i]);
}
augmented_array.sort(function(a, b) {
var ret;
ret = a[0] - b[0];
if (ret === 0) {
ret = a[1] - b[1];
}
return ret;
});
return augmented_array[offset][1];
};
Plugin.prototype.highestCol = function(array) {
return $.inArray(Math.max.apply(window, array), array);
};
Plugin.prototype.destroy = function() {
var $active_children, $container, active_class;
$container = this.$container;
$container.off("ss-arrange");
$container.off("ss-rearrange");
$container.off("ss-setTargetPosition");
$container.off("ss-destroy");
active_class = this.options.activeClass;
$active_children = $container.find("." + active_class);
if (this.options.enableDrag) {
$active_children.draggable('destroy');
}
if (this.options.enableCrossDrop) {
$container.droppable('destroy');
}
$active_children.removeClass(active_class);
return $container.removeClass(this.identifier);
};
return Plugin;
})();
return $.fn[pluginName] = function(options) {
return this.each(function() {
var bound_indentifier, old_class, _ref;
old_class = (_ref = $(this).attr("class").match(/shapeshifted_container_\w+/)) != null ? _ref[0] : void 0;
if (old_class) {
bound_indentifier = "resize." + old_class;
$(window).off(bound_indentifier);
$(this).removeClass(old_class);
}
return $.data(this, "plugin_" + pluginName, new Plugin(this, options));
});
};
})(jQuery, window, document);
}).call(this);
File diff suppressed because one or more lines are too long
+33
View File
@@ -0,0 +1,33 @@
{
"name": "jquery.shapeshift",
"version": "2.0",
"filename": "jquery.shapeshift.min.js",
"homepage": "https://github.com/McPants/jquery.shapeshift",
"description": "jQuery plugin which creates a column based grid system that allows drag and drop even between multiple containers.",
"keywords": [
"grid",
"column",
"drag",
"drop"
],
"maintainers": [
{
"name": "Scott Elwood",
"web": "http://www.scottelwood.com"
},
{
"name": "We the Media, Inc.",
"web": "http://www.wtmworldwide.com.com"
}
],
"repositories": [
{
"type": "git",
"url": "git@github.com:McPants/jquery.shapeshift.git"
}
],
"dependencies": {
"jquery": "~1.9.1",
"jqueryui": "~1.9.2"
}
}