/**
* @author Ryan Johnson
* @copyright 2008 PersonalGrid Corporation
* @package LivePipe UI
* @license MIT
* @url http://livepipe.net/control/selection
* @require prototype.js, effects.js, draggable.js, livepipe.js
*/
/*global window, document, Prototype, Element, Event, $, $$, $break, Control, Draggable */
if(typeof(Prototype) == "undefined") {
throw "Control.Selection requires Prototype to be loaded."; }
if(typeof(Object.Event) == "undefined") {
throw "Control.Selection requires Object.Event to be loaded."; }
Control.Selection = {
options: {
resize_layout_timeout: 125,
selected: Prototype.emptyFunction,
deselected: Prototype.emptyFunction,
change: Prototype.emptyFunction,
selection_id: 'control_selection',
selection_style: {
zIndex: 999,
cursor: 'default',
border: '1px dotted #000'
},
filter: function(element){
return true;
},
drag_proxy: false,
drag_proxy_threshold: 1,
drag_proxy_options: {}
},
selectableElements: [],
elements: [],
selectableObjects: [],
objects: [],
active: false,
container: false,
resizeTimeout: false,
load: function(options){
Control.Selection.options = Object.extend(Control.Selection.options,options || {});
Control.Selection.selection_div = $(document.createElement('div'));
Control.Selection.selection_div.id = Control.Selection.options.selection_id;
Control.Selection.selection_div.style.display = 'none';
Control.Selection.selection_div.setStyle(Control.Selection.options.selection_style);
Control.Selection.border_width = parseInt(Control.Selection.selection_div.getStyle('border-top-width'), 10) * 2;
Control.Selection.container = Prototype.Browser.IE ? window.container : window;
$(document.body).insert(Control.Selection.selection_div);
Control.Selection.enable();
if(Control.Selection.options.drag_proxy && typeof(Draggable) != 'undefined') {
Control.Selection.DragProxy.load(); }
Event.observe(window,'resize',function(){
if(Control.Selection.resizeTimeout) {
window.clearTimeout(Control.Selection.resizeTimeout); }
Control.Selection.resizeTimeout = window.setTimeout(Control.Selection.recalculateLayout,Control.Selection.options.resize_layout_timeout);
});
if(Prototype.Browser.IE){
var body = $$('body').first();
body.observe('mouseleave',Control.Selection.stop);
body.observe('mouseup',Control.Selection.stop);
}
},
enable: function(){
if(Prototype.Browser.IE){
document.onselectstart = function(){
return false;
};
}
Event.observe(Control.Selection.container,'mousedown',Control.Selection.start);
Event.observe(Control.Selection.container,'mouseup',Control.Selection.stop);
},
disable: function(){
if(Prototype.Browser.IE){
document.onselectstart = function(){
return true;
};
}
Event.stopObserving(Control.Selection.container,'mousedown',Control.Selection.start);
Event.stopObserving(Control.Selection.container,'mouseup',Control.Selection.stop);
},
recalculateLayout: function(){
Control.Selection.selectableElements.each(function(element){
var dimensions = element.getDimensions();
var offset = element.cumulativeOffset();
var scroll_offset = element.cumulativeScrollOffset();
if(!element._control_selection) {
element._control_selection = {}; }
element._control_selection.top = offset[1] - scroll_offset[1];
element._control_selection.left = offset[0] - scroll_offset[0];
element._control_selection.width = dimensions.width;
element._control_selection.height = dimensions.height;
});
},
addSelectable: function(element,object,activation_targets,activation_target_callback){
element = $(element);
if(activation_targets) {
activation_targets = activation_targets.each ? activation_targets : [activation_targets]; }
var dimensions = element.getDimensions();
var offset = Element.cumulativeOffset(element);
element._control_selection = {
activation_targets: activation_targets,
is_selected: false,
top: offset[1],
left: offset[0],
width: dimensions.width,
height: dimensions.height,
activationTargetMouseMove: function(){
Control.Selection.notify('activationTargetMouseMove',element);
if(activation_targets){
activation_targets.each(function(activation_target){
activation_target.stopObserving('mousemove',element._control_selection.activationTargetMouseMove);
});
}
Control.Selection.DragProxy.container.stopObserving('mousemove',element._control_selection.activationTargetMouseMove);
},
activationTargetMouseDown: function(event){
if(!Control.Selection.elements.include(element)) {
Control.Selection.select(element); }
Control.Selection.DragProxy.start(event);
Control.Selection.DragProxy.container.hide();
if(activation_targets){
activation_targets.each(function(activation_target){
activation_target.observe('mousemove',element._control_selection.activationTargetMouseMove);
});
}
Control.Selection.DragProxy.container.observe('mousemove',element._control_selection.activationTargetMouseMove);
},
activationTargetClick: function(){
Control.Selection.select(element);
if(typeof(activation_target_callback) == "function") {
activation_target_callback(); }
if(activation_targets){
activation_targets.each(function(activation_target){
activation_target.stopObserving('mousemove',element._control_selection.activationTargetMouseMove);
});
}
Control.Selection.DragProxy.container.stopObserving('mousemove',element._control_selection.activationTargetMouseMove);
}
};
element.onselectstart = function(){
return false;
};
element.unselectable = 'on';
element.style.MozUserSelect = 'none';
if(activation_targets){
activation_targets.each(function(activation_target){
activation_target.observe('mousedown',element._control_selection.activationTargetMouseDown);
activation_target.observe('click',element._control_selection.activationTargetClick);
});
}
Control.Selection.selectableElements.push(element);
Control.Selection.selectableObjects.push(object);
},
removeSelectable: function(element){
element = $(element);
if(element._control_selection.activation_targets){
element._control_selection.activation_targets.each(function(activation_target){
activation_target.stopObserving('mousedown',element._control_selection.activationTargetMouseDown);
});
element._control_selection.activation_targets.each(function(activation_target){
activation_target.stopObserving('click',element._control_selection.activationTargetClick);
});
}
element._control_selection = null;
element.onselectstart = function() {
return true;
};
element.unselectable = 'off';
element.style.MozUserSelect = '';
var position = 0;
Control.Selection.selectableElements.each(function(selectable_element,i){
if(selectable_element == element){
position = i;
throw $break;
}
});
Control.Selection.selectableElements = Control.Selection.selectableElements.without(element);
Control.Selection.selectableObjects = Control.Selection.selectableObjects.slice(0,position).concat(Control.Selection.selectableObjects.slice(position + 1));
},
select: function(selected_elements){
if(typeof(selected_elements) == "undefined" || !selected_elements) {
selected_elements = []; }
if(!selected_elements.each && !selected_elements._each) {
selected_elements = [selected_elements]; }
//comparing the arrays directly wouldn't equate to true in safari so we need to compare each item
var selected_items_have_changed = !(Control.Selection.elements.length == selected_elements.length && Control.Selection.elements.all(function(item,i){
return selected_elements[i] == item;
}));
if(!selected_items_have_changed) {
return; }
var selected_objects_indexed_by_element = {};
var selected_objects = selected_elements.collect(function(selected_element){
var selected_object = Control.Selection.selectableObjects[Control.Selection.selectableElements.indexOf(selected_element)];
selected_objects_indexed_by_element[selected_element] = selected_object;
return selected_object;
});
if(Control.Selection.elements.length === 0 && selected_elements.length !== 0){
selected_elements.each(function(element){
Control.Selection.notify('selected',element,selected_objects_indexed_by_element[element]);
});
}else{
Control.Selection.elements.each(function(element){
if(!selected_elements.include(element)){
Control.Selection.notify('deselected',element,selected_objects_indexed_by_element[element]);
}
});
selected_elements.each(function(element){
if(!Control.Selection.elements.include(element)){
Control.Selection.notify('selected',element,selected_objects_indexed_by_element[element]);
}
});
}
Control.Selection.elements = selected_elements;
Control.Selection.objects = selected_objects;
Control.Selection.notify('change',Control.Selection.elements,Control.Selection.objects);
},
deselect: function(){
if(Control.Selection.notify('deselect') === false) {
return false; }
Control.Selection.elements.each(function(element){
Control.Selection.notify('deselected',element,Control.Selection.selectableObjects[Control.Selection.selectableElements.indexOf(element)]);
});
Control.Selection.objects = [];
Control.Selection.elements = [];
Control.Selection.notify('change',Control.Selection.objects,Control.Selection.elements);
return true;
},
//private
start: function(event){
if(!event.isLeftClick() || Control.Selection.notify('start',event) === false) {
return false; }
if(!event.shiftKey && !event.altKey) {
Control.Selection.deselect(); }
Event.observe(Control.Selection.container,'mousemove',Control.Selection.onMouseMove);
Event.stop(event);
return false;
},
stop: function(){
Event.stopObserving(Control.Selection.container,'mousemove',Control.Selection.onMouseMove);
Control.Selection.active = false;
Control.Selection.selection_div.setStyle({
display: 'none',
top: null,
left: null,
width: null,
height: null
});
Control.Selection.start_mouse_coordinates = {};
Control.Selection.current_mouse_coordinates = {};
},
mouseCoordinatesFromEvent: function(event){
return {
x: Event.pointerX(event),
y: Event.pointerY(event)
};
},
onClick: function(event,element,source){
var selection = [];
if(event.shiftKey){
selection = Control.Selection.elements.clone();
if(!selection.include(element)) {
selection.push(element); }
}else if(event.altKey){
selection = Control.Selection.elements.clone();
if(selection.include(element)) {
selection = selection.without(element); }
}else{
selection = [element];
}
Control.Selection.select(selection);
if(source == 'click') {
Event.stop(event); }
},
onMouseMove: function(event){
if(!Control.Selection.active){
Control.Selection.active = true;
Control.Selection.start_mouse_coordinates = Control.Selection.mouseCoordinatesFromEvent(event);
}else{
Control.Selection.current_mouse_coordinates = Control.Selection.mouseCoordinatesFromEvent(event);
Control.Selection.drawSelectionDiv();
var current_selection = Control.Selection.selectableElements.findAll(function(element){
return Control.Selection.options.filter(element) && Control.Selection.elementWithinSelection(element);
});
if(event.shiftKey && !event.altKey){
Control.Selection.elements.each(function(element){
if(!current_selection.include(element)) {
current_selection.push(element); }
});
}else if(event.altKey && !event.shiftKey){
current_selection = Control.Selection.elements.findAll(function(element){
return !current_selection.include(element);
});
}
Control.Selection.select(current_selection);
}
},
drawSelectionDiv: function(){
if(Control.Selection.start_mouse_coordinates == Control.Selection.current_mouse_coordinates){
Control.Selection.selection_div.style.display = 'none';
}else{
Control.Selection.viewport = document.viewport.getDimensions();
Control.Selection.selection_div.style.position = 'absolute';
Control.Selection.current_direction = (Control.Selection.start_mouse_coordinates.y > Control.Selection.current_mouse_coordinates.y ? 'N' : 'S') + (Control.Selection.start_mouse_coordinates.x < Control.Selection.current_mouse_coordinates.x ? 'E' : 'W');
Control.Selection.selection_div.setStyle(Control.Selection['dimensionsFor' + Control.Selection.current_direction]());
Control.Selection.selection_div.style.display = 'block';
}
},
dimensionsForNW: function(){
return {
top: (Control.Selection.start_mouse_coordinates.y - (Control.Selection.start_mouse_coordinates.y - Control.Selection.current_mouse_coordinates.y)) + 'px',
left: (Control.Selection.start_mouse_coordinates.x - (Control.Selection.start_mouse_coordinates.x - Control.Selection.current_mouse_coordinates.x)) + 'px',
width: (Control.Selection.start_mouse_coordinates.x - Control.Selection.current_mouse_coordinates.x) + 'px',
height: (Control.Selection.start_mouse_coordinates.y - Control.Selection.current_mouse_coordinates.y) + 'px'
};
},
dimensionsForNE: function(){
return {
top: (Control.Selection.start_mouse_coordinates.y - (Control.Selection.start_mouse_coordinates.y - Control.Selection.current_mouse_coordinates.y)) + 'px',
left: Control.Selection.start_mouse_coordinates.x + 'px',
width: Math.min((Control.Selection.viewport.width - Control.Selection.start_mouse_coordinates.x) - Control.Selection.border_width,Control.Selection.current_mouse_coordinates.x - Control.Selection.start_mouse_coordinates.x) + 'px',
height: (Control.Selection.start_mouse_coordinates.y - Control.Selection.current_mouse_coordinates.y) + 'px'
};
},
dimensionsForSE: function(){
return {
top: Control.Selection.start_mouse_coordinates.y + 'px',
left: Control.Selection.start_mouse_coordinates.x + 'px',
width: Math.min((Control.Selection.viewport.width - Control.Selection.start_mouse_coordinates.x) - Control.Selection.border_width,Control.Selection.current_mouse_coordinates.x - Control.Selection.start_mouse_coordinates.x) + 'px',
height: Math.min((Control.Selection.viewport.height - Control.Selection.start_mouse_coordinates.y) - Control.Selection.border_width,Control.Selection.current_mouse_coordinates.y - Control.Selection.start_mouse_coordinates.y) + 'px'
};
},
dimensionsForSW: function(){
return {
top: Control.Selection.start_mouse_coordinates.y + 'px',
left: (Control.Selection.start_mouse_coordinates.x - (Control.Selection.start_mouse_coordinates.x - Control.Selection.current_mouse_coordinates.x)) + 'px',
width: (Control.Selection.start_mouse_coordinates.x - Control.Selection.current_mouse_coordinates.x) + 'px',
height: Math.min((Control.Selection.viewport.height - Control.Selection.start_mouse_coordinates.y) - Control.Selection.border_width,Control.Selection.current_mouse_coordinates.y - Control.Selection.start_mouse_coordinates.y) + 'px'
};
},
inBoundsForNW: function(element,selection){
return (
((element.left > selection.left || element.right > selection.left) && selection.right > element.left) &&
((element.top > selection.top || element.bottom > selection.top) && selection.bottom > element.top)
);
},
inBoundsForNE: function(element,selection){
return (
((element.left < selection.right || element.left < selection.right) && selection.left < element.right) &&
((element.top > selection.top || element.bottom > selection.top) && selection.bottom > element.top)
);
},
inBoundsForSE: function(element,selection){
return (
((element.left < selection.right || element.left < selection.right) && selection.left < element.right) &&
((element.bottom < selection.bottom || element.top < selection.bottom) && selection.top < element.bottom)
);
},
inBoundsForSW: function(element,selection){
return (
((element.left > selection.left || element.right > selection.left) && selection.right > element.left) &&
((element.bottom < selection.bottom || element.top < selection.bottom) && selection.top < element.bottom)
);
},
elementWithinSelection: function(element){
if(Control.Selection['inBoundsFor' + Control.Selection.current_direction]({
top: element._control_selection.top,
left: element._control_selection.left,
bottom: element._control_selection.top + element._control_selection.height,
right: element._control_selection.left + element._control_selection.width
},{
top: parseInt(Control.Selection.selection_div.style.top, 10),
left: parseInt(Control.Selection.selection_div.style.left, 10),
bottom: parseInt(Control.Selection.selection_div.style.top, 10) + parseInt(Control.Selection.selection_div.style.height, 10),
right: parseInt(Control.Selection.selection_div.style.left, 10) + parseInt(Control.Selection.selection_div.style.width, 10)
})){
element._control_selection.is_selected = true;
return true;
}else{
element._control_selection.is_selected = false;
return false;
}
},
DragProxy: {
active: false,
xorigin: 0,
yorigin: 0,
load: function(){
Control.Selection.DragProxy.container = $(document.createElement('div'));
Control.Selection.DragProxy.container.id = 'control_selection_drag_proxy';
Control.Selection.DragProxy.container.setStyle({
position: 'absolute',
top: '1px',
left: '1px',
zIndex: 99999
});
Control.Selection.DragProxy.container.hide();
document.body.appendChild(Control.Selection.DragProxy.container);
Control.Selection.observe('selected',Control.Selection.DragProxy.selected);
Control.Selection.observe('deselected',Control.Selection.DragProxy.deselected);
},
start: function(event){
if(event.isRightClick()){
Control.Selection.DragProxy.container.hide();
return;
}
if(Control.Selection.DragProxy.xorigin == Event.pointerX(event) && Control.Selection.DragProxy.yorigin == Event.pointerY(event)) {
return; }
Control.Selection.DragProxy.active = true;
Control.Selection.DragProxy.container.setStyle({
position: 'absolute',
top: Event.pointerY(event) + 'px',
left: Event.pointerX(event) + 'px'
});
Control.Selection.DragProxy.container.observe('mouseup',Control.Selection.DragProxy.onMouseUp);
Control.Selection.DragProxy.container.show();
Control.Selection.DragProxy.container._draggable = new Draggable(Control.Selection.DragProxy.container,Object.extend({
onEnd: Control.Selection.DragProxy.stop
},Control.Selection.options.drag_proxy_options));
Control.Selection.DragProxy.container._draggable.eventMouseDown(event);
Control.Selection.DragProxy.notify('start',Control.Selection.DragProxy.container,Control.Selection.elements);
},
stop: function(){
window.setTimeout(function(){
Control.Selection.DragProxy.active = false;
Control.Selection.DragProxy.container.hide();
if(Control.Selection.DragProxy.container._draggable){
Control.Selection.DragProxy.container._draggable.destroy();
Control.Selection.DragProxy.container._draggable = null;
}
Control.Selection.DragProxy.notify('stop');
},1);
},
onClick: function(event){
Control.Selection.DragProxy.xorigin = Event.pointerX(event);
Control.Selection.DragProxy.yorigin = Event.pointerY(event);
if(event.isRightClick()) {
Control.Selection.DragProxy.container.hide(); }
if(Control.Selection.elements.length >= Control.Selection.options.drag_proxy_threshold && !(event.shiftKey || event.altKey) && (Control.Selection.DragProxy.xorigin != Event.pointerX(event) || Control.Selection.DragProxy.yorigin != Event.pointerY(event))){
Control.Selection.DragProxy.start(event);
Event.stop(event);
}
},
onMouseUp: function(event){
Control.Selection.DragProxy.stop();
Control.Selection.DragProxy.container.stopObserving('mouseup',Control.Selection.DragProxy.onMouseUp);
},
selected: function(element){
element.observe('mousedown',Control.Selection.DragProxy.onClick);
},
deselected: function(element){
element.stopObserving('mousedown',Control.Selection.DragProxy.onClick);
}
}
};
Object.Event.extend(Control.Selection);
Object.Event.extend(Control.Selection.DragProxy);