From a8a0d32f3dee3c7a10e0ecf64b43f1dd5e5a7612 Mon Sep 17 00:00:00 2001 From: Jerry Bucci Date: Wed, 4 Jan 2012 16:48:35 -0500 Subject: [PATCH 1/2] 1. Made class compatible with Mootools 1.4+. 2. Added the use of Mootools Mask class. 3. Added destroyOnClose option. 4. Added methods to maintain parent/child relationships for LightFace instances. See the changelog for detail on the above changes. Signed-off-by: Jerry Bucci --- Assets/LightFace.css | 13 + Docs/LightFace.md | 39 +- Source/LightFace.js | 101 ++- changelog.md | 15 + tests/mask.html | 131 ++++ tests/mootools-more-drag-mask.js | 1103 ++++++++++++++++++++++++++++++ 6 files changed, 1390 insertions(+), 12 deletions(-) create mode 100644 tests/mask.html create mode 100644 tests/mootools-more-drag-mask.js diff --git a/Assets/LightFace.css b/Assets/LightFace.css index 64ba19d..a087ac4 100644 --- a/Assets/LightFace.css +++ b/Assets/LightFace.css @@ -1,8 +1,21 @@ +.lightfaceMask +{ + position: absolute; + opacity: 0.7; + filter: alpha(opacity=70); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); + z-index: 999; + background: #fff; +} + .lightface { margin: 0; padding: 0; border-collapse: collapse; +/* Removes focus() selection outline + outline: none; +*/ position: absolute; top: -9000px; left: -9000px; diff --git a/Docs/LightFace.md b/Docs/LightFace.md index dff28a1..78c1ae3 100644 --- a/Docs/LightFace.md +++ b/Docs/LightFace.md @@ -35,6 +35,10 @@ LightFace Method: constructor {#LightFace:constructor} * errorMessage - (*string*, defaults to '

The requested file could not be found.

') The error message displayed if a resource is not found. * resetOnScroll - (*boolean*, defaults to true) Keeps the modal box in the same place on the screen if the user scrolls. * baseClass - (*string*, defaults to 'lightface') The base class of the modal box. +* destroyOnClose - (*boolean*, defaults to false) Should the modal box destory it self when it closes? +* closeOnMaskClick - (*boolean*, defaults to false) Should the modal box close when the mask is clicked? +* maskClass - (*string*, defaults to 'lightfaceMask') The class of the mask. +* showMask - (*boolean*, defaults to true) Should the mask be displayed? If true this prevents user interaction with the page while the modal box is open. *the Mask class is provided in 'mootools-more-drag-mask.js' and also available with MooTools More* ### Returns: @@ -180,11 +184,12 @@ Closes the modal dialog. ### Syntax: - modal.close(fast); + modal.close(fast, bDestroy); ### Arguments: 1. fast - (*boolean*) If true, skips fading and quickly sets opacity to 0%. +2. bDestroy - (*boolean*) If true, the class is destroyed after it closes. When true, this overrides the destroyOnClose option. LightFace Method: fade {#LightFace:fade} --------------------------------------------------------------- @@ -222,7 +227,37 @@ Loads static content into the modal dialog. 1. content - (*string*) The content to place within the modal dialog. 2. title - (*string*) The title of the modal dialog. (not required) - + + +LightFace Method: attachDependent {#LightFace:attachDependent} +--------------------------------------------------------------- + +Adds an instance of LightFace as a dependent, when the parent instance closes it will close the dependents first. +A parent can have more than one dependent. + +### Syntax: + + modal.attachDependent(LightFaceObject); + +### Arguments: + +1. LightFaceObject - (*LightFace Object*) A dependent LightFace instance. + + +LightFace Method: attachParent {#LightFace:attachParent} +--------------------------------------------------------------- + +Adds an instance of LightFace as a parent, when the parent instance closes it will close the dependents first. +A dependent can only have one parent. + +### Syntax: + + modal.attachParent(LightFaceObject); + +### Arguments: + +1. LightFaceObject - (*LightFace Object*) A parent LightFace instance. + LightFace Method: destroy {#LightFace:destroy} --------------------------------------------------------------- diff --git a/Source/LightFace.js b/Source/LightFace.js index afd7e13..1d310c5 100644 --- a/Source/LightFace.js +++ b/Source/LightFace.js @@ -22,6 +22,7 @@ var LightFace = new Class({ options: { width: 'auto', height: 'auto', + destroyOnClose: false, draggable: false, title: '', buttons: [], @@ -37,7 +38,11 @@ var LightFace = new Class({ constrain: false, resetOnScroll: true, baseClass: 'lightface', - errorMessage: '

The requested file could not be found.

'/*, + errorMessage: '

The requested file could not be found.

', + /* Mask Options */ + closeOnMaskClick: false, + maskClass: 'lightfaceMask', + showMask: true/*, onOpen: $empty, onClose: $empty, onFade: $empty, @@ -56,17 +61,31 @@ var LightFace = new Class({ this.buttons = {}; this.resizeOnOpen = true; this.ie6 = typeof document.body.style.maxHeight == "undefined"; + this.oMask = null; + this.oParent = null; /* Parent LightFace object */ + this.dependents = []; /* Dependent LightFace objects */ this.draw(); }, draw: function() { + + //create modal mask + if (this.options.showMask && window['Mask'] != null) { + this.oMask = new Mask(null, { + 'class': this.options.maskClass, + onClick: function() { + if (this.options.closeOnMaskClick) this.close(); + }.bind(this) + }); + } //create main box this.box = new Element('table',{ 'class': this.options.baseClass, styles: { 'z-index': this.options.zIndex, - opacity: 0 + opacity: 0, + visibility: 'hidden' }, tween: { duration: this.options.fadeDuration, @@ -95,7 +114,7 @@ var LightFace = new Class({ cell.appendChild(this.contentBox); } else { - document.id(cell).setStyle('opacity',0.4); + document.id(cell).setStyles({ visibility: 'visible', opacity: 0.4 }); } } } @@ -121,7 +140,7 @@ var LightFace = new Class({ height: this.options.height } }).inject(this.contentBox); - + //button container this.footer = new Element('div',{ 'class': 'lightfaceFooter', @@ -134,7 +153,8 @@ var LightFace = new Class({ this.overlay = new Element('div',{ html: ' ', styles: { - opacity: 0 + opacity: 0, + visibility: 'hidden' }, 'class': 'lightfaceOverlay', tween: { @@ -162,7 +182,48 @@ var LightFace = new Class({ return this; }, - + + // Manage parent and dependent instances of LightFace. + attachDependent: function(oDependent) { + if (instanceOf(oDependent, LightFace) && this.dependents.indexOf(oDependent) === -1) { + this.dependents.push(oDependent); + oDependent.attachParent(this); + } + return this; + }, + attachParent: function(oParent) { + if (instanceOf(oParent, LightFace)) { + this.oParent = oParent; + if (oParent.dependents.indexOf(this) === -1) { + oParent.attachDependent(this); + } + } + return this; + }, + _closeDependents: function() { + if (this.dependents.length) { + this.dependents.each(function(oObj) { + oObj.close(true, this.options.destroyOnClose); + }, this); +// this.dependents.empty(); + } + return this; + }, + _detachDependent: function(oDependent) { + if (instanceOf(oDependent, LightFace)) { + this.dependents.erase(oDependent); + oDependent.oParent = null; + } + return this; + }, + _detachParent: function() { + if (this.oParent) { + this.oParent._detachDependent(this); + this.oParent = null; + } + return this; + }, + // Manage buttons addButton: function(title,clickEvent,color) { this.footer.setStyle('display','block'); @@ -202,19 +263,35 @@ var LightFace = new Class({ }, // Open and close box - close: function(fast) { + close: function(fast, bDestroy) { if(this.isOpen) { - this.box[fast ? 'setStyles' : 'tween']('opacity',0); + this._closeDependents(); + if (fast) { + this.box.setStyle('opacity', 0); + this.box.setStyle('visibility', 'hidden'); + if (this.oMask) this.oMask.hide(); + } + else { + this.box.get('tween').start('opacity', 0).chain(function() + { + this.box.setStyle('visibility', 'hidden'); + if (this.oMask) this.oMask.hide(); + if (bDestroy || this.options.destroyOnClose) this.destroy(); + }.bind(this)); + } this.fireEvent('close'); this._detachEvents(); this.isOpen = false; + if (fast && (bDestroy || this.options.destroyOnClose)) this.destroy(); } return this; }, open: function(fast) { if(!this.isOpen) { - this.box[fast ? 'setStyles' : 'tween']('opacity',1); + if (this.oMask) this.oMask.show(); + this.box.setStyle('visibility', 'visible'); + this.box[fast ? 'setStyle' : 'tween']('opacity', 1); if(this.resizeOnOpen) this._resize(); this.fireEvent('open'); this._attachEvents(); @@ -235,6 +312,7 @@ var LightFace = new Class({ fade: function(fade,delay) { this._ie6Size(); (function() { + this.overlay.setStyle('visibility', 'visible'); this.overlay.setStyle('opacity',fade || 1); }.bind(this)).delay(delay || 0); this.fireEvent('fade'); @@ -242,7 +320,7 @@ var LightFace = new Class({ }, unfade: function(delay) { (function() { - this.overlay.fade(0); + this.overlay.get('tween').start('opacity', 0).chain(function(){ this.element.setStyle('visibility', 'hidden'); }); }.bind(this)).delay(delay || this.options.fadeDelay); this.fireEvent('unfade'); return this; @@ -337,7 +415,10 @@ var LightFace = new Class({ // Cleanup destroy: function() { + this._closeDependents(); + this._detachParent(); this._detachEvents(); + if (this.oMask) this.oMask.destroy(); this.buttons.each(function(button) { button.removeEvents('click'); }); diff --git a/changelog.md b/changelog.md index a359872..7c915d6 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,21 @@ LightFace The following changes have been made to LightFace. +1/4/2012 +---------- + + As of Mootools 1.4 fade() & setStyle('opacity') no longer set the visibility property. Changed code to set visibility where opacity is set. + + Added the use of Mootools Mask class to make the plugin a true modal dialog box. Added options: showMask, maskClass, closeOnMaskClick. + + Add destroyOnClose option; if true the class will destory it's self when the dialog box closes. + + Added methods: [public] attachDependent(), attachParent(); [private] _closeDependents(), _detachDependent(), _detachParent(). + These methods maintain a parent/child relationship of LightFace instances. + + Added mask.html page to demonstrate the above changes. + Example: Click the "terms and conditions" link then click "I Agree" button. + With the Confirm dialog box open click the white space outside the parent window (this is the mask). + This action will close the Confirm dialog box then the parent dialog box. + + Added mootools-more-drag-mask.js for use by the mask.html page. + + Added lightfaceMask to LightFace.css. + + Tag updated from .96 to .97 + 11/17/2010 ---------- + Updated destroy() method to remove reference to node so that videos stop playing when destroy() is called. diff --git a/tests/mask.html b/tests/mask.html new file mode 100644 index 0000000..eadc75f --- /dev/null +++ b/tests/mask.html @@ -0,0 +1,131 @@ + + + + Mask LightFace Test + + + + + + + + + + + + + +

Simple demo

+ + + + + + + + + + + + + + +
Title
content
+ +

+ +

Terms and conditions demo

+

Do you agree to our terms and conditions?


+ +

Right-Click Demo

+

Right click this link to set the dialog here.

+ +

 

 

 

 

 

 

+ + + \ No newline at end of file diff --git a/tests/mootools-more-drag-mask.js b/tests/mootools-more-drag-mask.js new file mode 100644 index 0000000..9128aa0 --- /dev/null +++ b/tests/mootools-more-drag-mask.js @@ -0,0 +1,1103 @@ +// MooTools: the javascript framework. +// Load this file's selection again by visiting: http://mootools.net/more/480f2638ac3a0693a2e0448d2a731023 +// Or build this file again with packager using: packager build More/Drag More/Mask +/* +--- + +script: More.js + +name: More + +description: MooTools More + +license: MIT-style license + +authors: + - Guillermo Rauch + - Thomas Aylott + - Scott Kyle + - Arian Stolwijk + - Tim Wienk + - Christoph Pojer + - Aaron Newton + - Jacob Thornton + +requires: + - Core/MooTools + +provides: [MooTools.More] + +... +*/ + +MooTools.More = { + 'version': '1.4.0.1', + 'build': 'a4244edf2aa97ac8a196fc96082dd35af1abab87' +}; + + +/* +--- + +script: Drag.js + +name: Drag + +description: The base Drag Class. Can be used to drag and resize Elements using mouse events. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - /MooTools.More + +provides: [Drag] +... + +*/ + +var Drag = new Class({ + + Implements: [Events, Options], + + options: {/* + onBeforeStart: function(thisElement){}, + onStart: function(thisElement, event){}, + onSnap: function(thisElement){}, + onDrag: function(thisElement, event){}, + onCancel: function(thisElement){}, + onComplete: function(thisElement, event){},*/ + snap: 6, + unit: 'px', + grid: false, + style: true, + limit: false, + handle: false, + invert: false, + preventDefault: false, + stopPropagation: false, + modifiers: {x: 'left', y: 'top'} + }, + + initialize: function(){ + var params = Array.link(arguments, { + 'options': Type.isObject, + 'element': function(obj){ + return obj != null; + } + }); + + this.element = document.id(params.element); + this.document = this.element.getDocument(); + this.setOptions(params.options || {}); + var htype = typeOf(this.options.handle); + this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element; + this.mouse = {'now': {}, 'pos': {}}; + this.value = {'start': {}, 'now': {}}; + + this.selection = (Browser.ie) ? 'selectstart' : 'mousedown'; + + + if (Browser.ie && !Drag.ondragstartFixed){ + document.ondragstart = Function.from(false); + Drag.ondragstartFixed = true; + } + + this.bound = { + start: this.start.bind(this), + check: this.check.bind(this), + drag: this.drag.bind(this), + stop: this.stop.bind(this), + cancel: this.cancel.bind(this), + eventStop: Function.from(false) + }; + this.attach(); + }, + + attach: function(){ + this.handles.addEvent('mousedown', this.bound.start); + return this; + }, + + detach: function(){ + this.handles.removeEvent('mousedown', this.bound.start); + return this; + }, + + start: function(event){ + var options = this.options; + + if (event.rightClick) return; + + if (options.preventDefault) event.preventDefault(); + if (options.stopPropagation) event.stopPropagation(); + this.mouse.start = event.page; + + this.fireEvent('beforeStart', this.element); + + var limit = options.limit; + this.limit = {x: [], y: []}; + + var z, coordinates; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + + var style = this.element.getStyle(options.modifiers[z]); + + // Some browsers (IE and Opera) don't always return pixels. + if (style && !style.match(/px$/)){ + if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent()); + style = coordinates[options.modifiers[z]]; + } + + if (options.style) this.value.now[z] = (style || 0).toInt(); + else this.value.now[z] = this.element[options.modifiers[z]]; + + if (options.invert) this.value.now[z] *= -1; + + this.mouse.pos[z] = event.page[z] - this.value.now[z]; + + if (limit && limit[z]){ + var i = 2; + while (i--){ + var limitZI = limit[z][i]; + if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI; + } + } + } + + if (typeOf(this.options.grid) == 'number') this.options.grid = { + x: this.options.grid, + y: this.options.grid + }; + + var events = { + mousemove: this.bound.check, + mouseup: this.bound.cancel + }; + events[this.selection] = this.bound.eventStop; + this.document.addEvents(events); + }, + + check: function(event){ + if (this.options.preventDefault) event.preventDefault(); + var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); + if (distance > this.options.snap){ + this.cancel(); + this.document.addEvents({ + mousemove: this.bound.drag, + mouseup: this.bound.stop + }); + this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element); + } + }, + + drag: function(event){ + var options = this.options; + + if (options.preventDefault) event.preventDefault(); + this.mouse.now = event.page; + + for (var z in options.modifiers){ + if (!options.modifiers[z]) continue; + this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; + + if (options.invert) this.value.now[z] *= -1; + + if (options.limit && this.limit[z]){ + if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){ + this.value.now[z] = this.limit[z][1]; + } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){ + this.value.now[z] = this.limit[z][0]; + } + } + + if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]); + + if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit); + else this.element[options.modifiers[z]] = this.value.now[z]; + } + + this.fireEvent('drag', [this.element, event]); + }, + + cancel: function(event){ + this.document.removeEvents({ + mousemove: this.bound.check, + mouseup: this.bound.cancel + }); + if (event){ + this.document.removeEvent(this.selection, this.bound.eventStop); + this.fireEvent('cancel', this.element); + } + }, + + stop: function(event){ + var events = { + mousemove: this.bound.drag, + mouseup: this.bound.stop + }; + events[this.selection] = this.bound.eventStop; + this.document.removeEvents(events); + if (event) this.fireEvent('complete', [this.element, event]); + } + +}); + +Element.implement({ + + makeResizable: function(options){ + var drag = new Drag(this, Object.merge({ + modifiers: { + x: 'width', + y: 'height' + } + }, options)); + + this.store('resizer', drag); + return drag.addEvent('drag', function(){ + this.fireEvent('resize', drag); + }.bind(this)); + } + +}); + + +/* +--- + +script: Class.Binds.js + +name: Class.Binds + +description: Automagically binds specified methods in a class to the instance of the class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - /MooTools.More + +provides: [Class.Binds] + +... +*/ + +Class.Mutators.Binds = function(binds){ + if (!this.prototype.initialize) this.implement('initialize', function(){}); + return Array.from(binds).concat(this.prototype.Binds || []); +}; + +Class.Mutators.initialize = function(initialize){ + return function(){ + Array.from(this.Binds).each(function(name){ + var original = this[name]; + if (original) this[name] = original.bind(this); + }, this); + return initialize.apply(this, arguments); + }; +}; + + +/* +--- + +script: Element.Measure.js + +name: Element.Measure + +description: Extends the Element native object to include methods useful in measuring dimensions. + +credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - Core/Element.Dimensions + - /MooTools.More + +provides: [Element.Measure] + +... +*/ + +(function(){ + +var getStylesList = function(styles, planes){ + var list = []; + Object.each(planes, function(directions){ + Object.each(directions, function(edge){ + styles.each(function(style){ + list.push(style + '-' + edge + (style == 'border' ? '-width' : '')); + }); + }); + }); + return list; +}; + +var calculateEdgeSize = function(edge, styles){ + var total = 0; + Object.each(styles, function(value, style){ + if (style.test(edge)) total = total + value.toInt(); + }); + return total; +}; + +var isVisible = function(el){ + return !!(!el || el.offsetHeight || el.offsetWidth); +}; + + +Element.implement({ + + measure: function(fn){ + if (isVisible(this)) return fn.call(this); + var parent = this.getParent(), + toMeasure = []; + while (!isVisible(parent) && parent != document.body){ + toMeasure.push(parent.expose()); + parent = parent.getParent(); + } + var restore = this.expose(), + result = fn.call(this); + restore(); + toMeasure.each(function(restore){ + restore(); + }); + return result; + }, + + expose: function(){ + if (this.getStyle('display') != 'none') return function(){}; + var before = this.style.cssText; + this.setStyles({ + display: 'block', + position: 'absolute', + visibility: 'hidden' + }); + return function(){ + this.style.cssText = before; + }.bind(this); + }, + + getDimensions: function(options){ + options = Object.merge({computeSize: false}, options); + var dim = {x: 0, y: 0}; + + var getSize = function(el, options){ + return (options.computeSize) ? el.getComputedSize(options) : el.getSize(); + }; + + var parent = this.getParent('body'); + + if (parent && this.getStyle('display') == 'none'){ + dim = this.measure(function(){ + return getSize(this, options); + }); + } else if (parent){ + try { //safari sometimes crashes here, so catch it + dim = getSize(this, options); + }catch(e){} + } + + return Object.append(dim, (dim.x || dim.x === 0) ? { + width: dim.x, + height: dim.y + } : { + x: dim.width, + y: dim.height + } + ); + }, + + getComputedSize: function(options){ + + + options = Object.merge({ + styles: ['padding','border'], + planes: { + height: ['top','bottom'], + width: ['left','right'] + }, + mode: 'both' + }, options); + + var styles = {}, + size = {width: 0, height: 0}, + dimensions; + + if (options.mode == 'vertical'){ + delete size.width; + delete options.planes.width; + } else if (options.mode == 'horizontal'){ + delete size.height; + delete options.planes.height; + } + + getStylesList(options.styles, options.planes).each(function(style){ + styles[style] = this.getStyle(style).toInt(); + }, this); + + Object.each(options.planes, function(edges, plane){ + + var capitalized = plane.capitalize(), + style = this.getStyle(plane); + + if (style == 'auto' && !dimensions) dimensions = this.getDimensions(); + + style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt(); + size['total' + capitalized] = style; + + edges.each(function(edge){ + var edgesize = calculateEdgeSize(edge, styles); + size['computed' + edge.capitalize()] = edgesize; + size['total' + capitalized] += edgesize; + }); + + }, this); + + return Object.append(size, styles); + } + +}); + +})(); + + +/* +--- + +script: Element.Position.js + +name: Element.Position + +description: Extends the Element native object to include methods useful positioning elements relative to others. + +license: MIT-style license + +authors: + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Options + - Core/Element.Dimensions + - Element.Measure + +provides: [Element.Position] + +... +*/ + +(function(original){ + +var local = Element.Position = { + + options: {/* + edge: false, + returnPos: false, + minimum: {x: 0, y: 0}, + maximum: {x: 0, y: 0}, + relFixedPosition: false, + ignoreMargins: false, + ignoreScroll: false, + allowNegative: false,*/ + relativeTo: document.body, + position: { + x: 'center', //left, center, right + y: 'center' //top, center, bottom + }, + offset: {x: 0, y: 0} + }, + + getOptions: function(element, options){ + options = Object.merge({}, local.options, options); + local.setPositionOption(options); + local.setEdgeOption(options); + local.setOffsetOption(element, options); + local.setDimensionsOption(element, options); + return options; + }, + + setPositionOption: function(options){ + options.position = local.getCoordinateFromValue(options.position); + }, + + setEdgeOption: function(options){ + var edgeOption = local.getCoordinateFromValue(options.edge); + options.edge = edgeOption ? edgeOption : + (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} : + {x: 'left', y: 'top'}; + }, + + setOffsetOption: function(element, options){ + var parentOffset = {x: 0, y: 0}, + offsetParent = element.measure(function(){ + return document.id(this.getOffsetParent()); + }), + parentScroll = offsetParent.getScroll(); + + if (!offsetParent || offsetParent == element.getDocument().body) return; + parentOffset = offsetParent.measure(function(){ + var position = this.getPosition(); + if (this.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.x += scroll.x; + position.y += scroll.y; + } + return position; + }); + + options.offset = { + parentPositioned: offsetParent != document.id(options.relativeTo), + x: options.offset.x - parentOffset.x + parentScroll.x, + y: options.offset.y - parentOffset.y + parentScroll.y + }; + }, + + setDimensionsOption: function(element, options){ + options.dimensions = element.getDimensions({ + computeSize: true, + styles: ['padding', 'border', 'margin'] + }); + }, + + getPosition: function(element, options){ + var position = {}; + options = local.getOptions(element, options); + var relativeTo = document.id(options.relativeTo) || document.body; + + local.setPositionCoordinates(options, position, relativeTo); + if (options.edge) local.toEdge(position, options); + + var offset = options.offset; + position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt(); + position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt(); + + local.toMinMax(position, options); + + if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position); + if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position); + if (options.ignoreMargins) local.toIgnoreMargins(position, options); + + position.left = Math.ceil(position.left); + position.top = Math.ceil(position.top); + delete position.x; + delete position.y; + + return position; + }, + + setPositionCoordinates: function(options, position, relativeTo){ + var offsetY = options.offset.y, + offsetX = options.offset.x, + calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(), + top = calc.y, + left = calc.x, + winSize = window.getSize(); + + switch(options.position.x){ + case 'left': position.x = left + offsetX; break; + case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break; + default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break; + } + + switch(options.position.y){ + case 'top': position.y = top + offsetY; break; + case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break; + default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break; + } + }, + + toMinMax: function(position, options){ + var xy = {left: 'x', top: 'y'}, value; + ['minimum', 'maximum'].each(function(minmax){ + ['left', 'top'].each(function(lr){ + value = options[minmax] ? options[minmax][xy[lr]] : null; + if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value; + }); + }); + }, + + toRelFixedPosition: function(relativeTo, position){ + var winScroll = window.getScroll(); + position.top += winScroll.y; + position.left += winScroll.x; + }, + + toIgnoreScroll: function(relativeTo, position){ + var relScroll = relativeTo.getScroll(); + position.top -= relScroll.y; + position.left -= relScroll.x; + }, + + toIgnoreMargins: function(position, options){ + position.left += options.edge.x == 'right' + ? options.dimensions['margin-right'] + : (options.edge.x != 'center' + ? -options.dimensions['margin-left'] + : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2)); + + position.top += options.edge.y == 'bottom' + ? options.dimensions['margin-bottom'] + : (options.edge.y != 'center' + ? -options.dimensions['margin-top'] + : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2)); + }, + + toEdge: function(position, options){ + var edgeOffset = {}, + dimensions = options.dimensions, + edge = options.edge; + + switch(edge.x){ + case 'left': edgeOffset.x = 0; break; + case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break; + // center + default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break; + } + + switch(edge.y){ + case 'top': edgeOffset.y = 0; break; + case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break; + // center + default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break; + } + + position.x += edgeOffset.x; + position.y += edgeOffset.y; + }, + + getCoordinateFromValue: function(option){ + if (typeOf(option) != 'string') return option; + option = option.toLowerCase(); + + return { + x: option.test('left') ? 'left' + : (option.test('right') ? 'right' : 'center'), + y: option.test(/upper|top/) ? 'top' + : (option.test('bottom') ? 'bottom' : 'center') + }; + } + +}; + +Element.implement({ + + position: function(options){ + if (options && (options.x != null || options.y != null)){ + return (original ? original.apply(this, arguments) : this); + } + var position = this.setStyle('position', 'absolute').calculatePosition(options); + return (options && options.returnPos) ? position : this.setStyles(position); + }, + + calculatePosition: function(options){ + return local.getPosition(this, options); + } + +}); + +})(Element.prototype.position); + + +/* +--- + +script: Class.Occlude.js + +name: Class.Occlude + +description: Prevents a class from being applied to a DOM element twice. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Class + - Core/Element + - /MooTools.More + +provides: [Class.Occlude] + +... +*/ + +Class.Occlude = new Class({ + + occlude: function(property, element){ + element = document.id(element || this.element); + var instance = element.retrieve(property || this.property); + if (instance && !this.occluded) + return (this.occluded = instance); + + this.occluded = false; + element.store(property || this.property, this); + return this.occluded; + } + +}); + + +/* +--- + +script: IframeShim.js + +name: IframeShim + +description: Defines IframeShim, a class for obscuring select lists and flash objects in IE. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Style + - Core/Options + - Core/Events + - /Element.Position + - /Class.Occlude + +provides: [IframeShim] + +... +*/ + +var IframeShim = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + className: 'iframeShim', + src: 'javascript:false;document.write("");', + display: false, + zIndex: null, + margin: 0, + offset: {x: 0, y: 0}, + browsers: (Browser.ie6 || (Browser.firefox && Browser.version < 3 && Browser.Platform.mac)) + }, + + property: 'IframeShim', + + initialize: function(element, options){ + this.element = document.id(element); + if (this.occlude()) return this.occluded; + this.setOptions(options); + this.makeShim(); + return this; + }, + + makeShim: function(){ + if (this.options.browsers){ + var zIndex = this.element.getStyle('zIndex').toInt(); + + if (!zIndex){ + zIndex = 1; + var pos = this.element.getStyle('position'); + if (pos == 'static' || !pos) this.element.setStyle('position', 'relative'); + this.element.setStyle('zIndex', zIndex); + } + zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1; + if (zIndex < 0) zIndex = 1; + this.shim = new Element('iframe', { + src: this.options.src, + scrolling: 'no', + frameborder: 0, + styles: { + zIndex: zIndex, + position: 'absolute', + border: 'none', + filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)' + }, + 'class': this.options.className + }).store('IframeShim', this); + var inject = (function(){ + this.shim.inject(this.element, 'after'); + this[this.options.display ? 'show' : 'hide'](); + this.fireEvent('inject'); + }).bind(this); + if (!IframeShim.ready) window.addEvent('load', inject); + else inject(); + } else { + this.position = this.hide = this.show = this.dispose = Function.from(this); + } + }, + + position: function(){ + if (!IframeShim.ready || !this.shim) return this; + var size = this.element.measure(function(){ + return this.getSize(); + }); + if (this.options.margin != undefined){ + size.x = size.x - (this.options.margin * 2); + size.y = size.y - (this.options.margin * 2); + this.options.offset.x += this.options.margin; + this.options.offset.y += this.options.margin; + } + this.shim.set({width: size.x, height: size.y}).position({ + relativeTo: this.element, + offset: this.options.offset + }); + return this; + }, + + hide: function(){ + if (this.shim) this.shim.setStyle('display', 'none'); + return this; + }, + + show: function(){ + if (this.shim) this.shim.setStyle('display', 'block'); + return this.position(); + }, + + dispose: function(){ + if (this.shim) this.shim.dispose(); + return this; + }, + + destroy: function(){ + if (this.shim) this.shim.destroy(); + return this; + } + +}); + +window.addEvent('load', function(){ + IframeShim.ready = true; +}); + + +/* +--- + +script: Mask.js + +name: Mask + +description: Creates a mask element to cover another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - /Class.Binds + - /Element.Position + - /IframeShim + +provides: [Mask] + +... +*/ + +var Mask = new Class({ + + Implements: [Options, Events], + + Binds: ['position'], + + options: {/* + onShow: function(){}, + onHide: function(){}, + onDestroy: function(){}, + onClick: function(event){}, + inject: { + where: 'after', + target: null, + }, + hideOnClick: false, + id: null, + destroyOnHide: false,*/ + style: {}, + 'class': 'mask', + maskMargins: false, + useIframeShim: true, + iframeShimOptions: {} + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('mask', this); + this.setOptions(options); + this.render(); + this.inject(); + }, + + render: function(){ + this.element = new Element('div', { + 'class': this.options['class'], + id: this.options.id || 'mask-' + String.uniqueID(), + styles: Object.merge({}, this.options.style, { + display: 'none' + }), + events: { + click: function(event){ + this.fireEvent('click', event); + if (this.options.hideOnClick) this.hide(); + }.bind(this) + } + }); + + this.hidden = true; + }, + + toElement: function(){ + return this.element; + }, + + inject: function(target, where){ + where = where || (this.options.inject ? this.options.inject.where : '') || this.target == document.body ? 'inside' : 'after'; + target = target || (this.options.inject && this.options.inject.target) || this.target; + + this.element.inject(target, where); + + if (this.options.useIframeShim){ + this.shim = new IframeShim(this.element, this.options.iframeShimOptions); + + this.addEvents({ + show: this.shim.show.bind(this.shim), + hide: this.shim.hide.bind(this.shim), + destroy: this.shim.destroy.bind(this.shim) + }); + } + }, + + position: function(){ + this.resize(this.options.width, this.options.height); + + this.element.position({ + relativeTo: this.target, + position: 'topLeft', + ignoreMargins: !this.options.maskMargins, + ignoreScroll: this.target == document.body + }); + + return this; + }, + + resize: function(x, y){ + var opt = { + styles: ['padding', 'border'] + }; + if (this.options.maskMargins) opt.styles.push('margin'); + + var dim = this.target.getComputedSize(opt); + if (this.target == document.body){ + this.element.setStyles({width: 0, height: 0}); + var win = window.getScrollSize(); + if (dim.totalHeight < win.y) dim.totalHeight = win.y; + if (dim.totalWidth < win.x) dim.totalWidth = win.x; + } + this.element.setStyles({ + width: Array.pick([x, dim.totalWidth, dim.x]), + height: Array.pick([y, dim.totalHeight, dim.y]) + }); + + return this; + }, + + show: function(){ + if (!this.hidden) return this; + + window.addEvent('resize', this.position); + this.position(); + this.showMask.apply(this, arguments); + + return this; + }, + + showMask: function(){ + this.element.setStyle('display', 'block'); + this.hidden = false; + this.fireEvent('show'); + }, + + hide: function(){ + if (this.hidden) return this; + + window.removeEvent('resize', this.position); + this.hideMask.apply(this, arguments); + if (this.options.destroyOnHide) return this.destroy(); + + return this; + }, + + hideMask: function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + }, + + toggle: function(){ + this[this.hidden ? 'show' : 'hide'](); + }, + + destroy: function(){ + this.hide(); + this.element.destroy(); + this.fireEvent('destroy'); + this.target.eliminate('mask'); + } + +}); + +Element.Properties.mask = { + + set: function(options){ + var mask = this.retrieve('mask'); + if (mask) mask.destroy(); + return this.eliminate('mask').store('mask:options', options); + }, + + get: function(){ + var mask = this.retrieve('mask'); + if (!mask){ + mask = new Mask(this, this.retrieve('mask:options')); + this.store('mask', mask); + } + return mask; + } + +}; + +Element.implement({ + + mask: function(options){ + if (options) this.set('mask', options); + this.get('mask').show(); + return this; + }, + + unmask: function(){ + this.get('mask').hide(); + return this; + } + +}); + From 2e271a055b0d711402c7184373bab34d3fe93997 Mon Sep 17 00:00:00 2001 From: Jerry Bucci Date: Thu, 5 Jan 2012 13:02:20 -0500 Subject: [PATCH 2/2] this.buttons is incorrectly redefined from an Object to an Array. The code uses it as an Object. I removed the code declaring it as an Array. Updated the destroy method to loop this.buttons as an Object. The previous code looped it as an Array but length was aways 0 since this.buttons is used as an Object. Signed-off-by: Jerry Bucci --- Source/LightFace.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/LightFace.js b/Source/LightFace.js index 1d310c5..e35009c 100644 --- a/Source/LightFace.js +++ b/Source/LightFace.js @@ -170,7 +170,6 @@ var LightFace = new Class({ } //create initial buttons - this.buttons = []; if(this.options.buttons.length) { this.options.buttons.each(function(button) { this.addButton(button.title,button.event,button.color); @@ -419,7 +418,7 @@ var LightFace = new Class({ this._detachParent(); this._detachEvents(); if (this.oMask) this.oMask.destroy(); - this.buttons.each(function(button) { + Object.each(this.buttons, function(button) { button.removeEvents('click'); }); this.box.dispose();