/** * @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);