Adding mapbox-gl branch

This commit is contained in:
Andreas Hocevar
2015-03-16 18:50:27 +01:00
parent 7985f030fa
commit 57ee7f52fd
3109 changed files with 943365 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: eae@google.com (Emil A Eklund)
Author: dgajda@google.com (Damian Gajda)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.AbstractDragDrop
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.AbstractDragDropTest');
</script>
<style>
#cont {
position: relative;
border: 1px solid black;
height: 100px;
width: 100px;
}
#cont div {
position: absolute;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cont" style="">
</div>
<button onclick="drawTargets(targets, 10)">
draw targets 1
</button>
<br />
<button onclick="drawTargets(targets2, 1)">
draw targets 2
</button>
<br />
<button onclick="drawTargets(targets3, 10)">
draw targets 3
</button>
<div id="container1">
<div id="child1">
</div>
<div id="child2">
</div>
</div>
</body>
</html>
@@ -0,0 +1,636 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.AbstractDragDropTest');
goog.setTestOnly('goog.fx.AbstractDragDropTest');
goog.require('goog.array');
goog.require('goog.events.EventType');
goog.require('goog.functions');
goog.require('goog.fx.AbstractDragDrop');
goog.require('goog.fx.DragDropItem');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.style');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
var targets = [
{ box_: new goog.math.Box(0, 3, 1, 1) },
{ box_: new goog.math.Box(0, 7, 2, 6) },
{ box_: new goog.math.Box(2, 2, 3, 1) },
{ box_: new goog.math.Box(4, 1, 6, 1) },
{ box_: new goog.math.Box(4, 9, 7, 6) },
{ box_: new goog.math.Box(9, 9, 10, 1) }
];
var targets2 = [
{ box_: new goog.math.Box(10, 50, 20, 10) },
{ box_: new goog.math.Box(20, 50, 30, 10) },
{ box_: new goog.math.Box(60, 50, 70, 10) },
{ box_: new goog.math.Box(70, 50, 80, 10) }
];
var targets3 = [
{ box_: new goog.math.Box(0, 4, 1, 1) },
{ box_: new goog.math.Box(1, 6, 4, 5) },
{ box_: new goog.math.Box(5, 5, 6, 2) },
{ box_: new goog.math.Box(2, 1, 5, 0) }
];
/**
* Test the utility function which tells how two one dimensional ranges
* overlap.
*/
function testRangeOverlap() {
assertEquals(RangeOverlap.LEFT, rangeOverlap(1, 2, 3, 4));
assertEquals(RangeOverlap.LEFT, rangeOverlap(2, 3, 3, 4));
assertEquals(RangeOverlap.LEFT_IN, rangeOverlap(1, 3, 2, 4));
assertEquals(RangeOverlap.IN, rangeOverlap(1, 3, 1, 4));
assertEquals(RangeOverlap.IN, rangeOverlap(2, 3, 1, 4));
assertEquals(RangeOverlap.IN, rangeOverlap(3, 4, 1, 4));
assertEquals(RangeOverlap.RIGHT_IN, rangeOverlap(2, 4, 1, 3));
assertEquals(RangeOverlap.RIGHT, rangeOverlap(2, 3, 1, 2));
assertEquals(RangeOverlap.RIGHT, rangeOverlap(3, 4, 1, 2));
assertEquals(RangeOverlap.CONTAINS, rangeOverlap(1, 4, 2, 3));
}
/**
* An enum describing how two ranges overlap (non-symmetrical relation).
* @enum {number}
*/
RangeOverlap = {
LEFT: 1, // First range is placed to the left of the second.
LEFT_IN: 2, // First range overlaps on the left side of the second.
IN: 3, // First range is completely contained in the second.
RIGHT_IN: 4, // First range overlaps on the right side of the second.
RIGHT: 5, // First range is placed to the right side of the second.
CONTAINS: 6 // First range contains the second.
};
/**
* Computes how two one dimentional ranges overlap.
*
* @param {number} left1 Left inclusive bound of the first range.
* @param {number} right1 Right exclusive bound of the first range.
* @param {number} left2 Left inclusive bound of the second range.
* @param {number} right2 Right exclusive bound of the second range.
* @return {RangeOverlap} The enum value describing the type of the overlap.
*/
function rangeOverlap(left1, right1, left2, right2) {
if (right1 <= left2) return RangeOverlap.LEFT;
if (left1 >= right2) return RangeOverlap.RIGHT;
var leftIn = left1 >= left2;
var rightIn = right1 <= right2;
if (leftIn && rightIn) return RangeOverlap.IN;
if (leftIn) return RangeOverlap.RIGHT_IN;
if (rightIn) return RangeOverlap.LEFT_IN;
return RangeOverlap.CONTAINS;
}
/**
* Tells whether two boxes overlap.
*
* @param {goog.math.Box} box1 First box in question.
* @param {goog.math.Box} box2 Second box in question.
* @return {boolean} Whether boxes overlap in any way.
*/
function boxOverlaps(box1, box2) {
var horizontalOverlap = rangeOverlap(
box1.left, box1.right, box2.left, box2.right);
var verticalOverlap = rangeOverlap(
box1.top, box1.bottom, box2.top, box2.bottom);
return horizontalOverlap != RangeOverlap.LEFT &&
horizontalOverlap != RangeOverlap.RIGHT &&
verticalOverlap != RangeOverlap.LEFT &&
verticalOverlap != RangeOverlap.RIGHT;
}
/**
* Tests if the utility function to compute box overlapping functions properly.
*/
function testBoxOverlaps() {
// Overlapping tests.
var box2 = new goog.math.Box(1, 4, 4, 1);
// Corner overlaps.
assertTrue('NW overlap', boxOverlaps(new goog.math.Box(0, 2, 2, 0), box2));
assertTrue('NE overlap', boxOverlaps(new goog.math.Box(0, 5, 2, 3), box2));
assertTrue('SE overlap', boxOverlaps(new goog.math.Box(3, 5, 5, 3), box2));
assertTrue('SW overlap', boxOverlaps(new goog.math.Box(3, 2, 5, 0), box2));
// Inside.
assertTrue('Inside overlap',
boxOverlaps(new goog.math.Box(2, 3, 3, 2), box2));
// Around.
assertTrue('Outside overlap',
boxOverlaps(new goog.math.Box(0, 5, 5, 0), box2));
// Edge overlaps.
assertTrue('N overlap', boxOverlaps(new goog.math.Box(0, 3, 2, 2), box2));
assertTrue('E overlap', boxOverlaps(new goog.math.Box(2, 5, 3, 3), box2));
assertTrue('S overlap', boxOverlaps(new goog.math.Box(3, 3, 5, 2), box2));
assertTrue('W overlap', boxOverlaps(new goog.math.Box(2, 2, 3, 0), box2));
assertTrue('N-in overlap', boxOverlaps(new goog.math.Box(0, 5, 2, 0), box2));
assertTrue('E-in overlap', boxOverlaps(new goog.math.Box(0, 5, 5, 3), box2));
assertTrue('S-in overlap', boxOverlaps(new goog.math.Box(3, 5, 5, 0), box2));
assertTrue('W-in overlap', boxOverlaps(new goog.math.Box(0, 2, 5, 0), box2));
// Does not overlap.
var box2 = new goog.math.Box(3, 6, 6, 3);
// Along the edge - shorter.
assertFalse('N-in no overlap',
boxOverlaps(new goog.math.Box(1, 5, 2, 4), box2));
assertFalse('E-in no overlap',
boxOverlaps(new goog.math.Box(4, 8, 5, 7), box2));
assertFalse('S-in no overlap',
boxOverlaps(new goog.math.Box(7, 5, 8, 4), box2));
assertFalse('N-in no overlap',
boxOverlaps(new goog.math.Box(4, 2, 5, 1), box2));
// By the corner.
assertFalse('NE no overlap',
boxOverlaps(new goog.math.Box(1, 8, 2, 7), box2));
assertFalse('SE no overlap',
boxOverlaps(new goog.math.Box(7, 8, 8, 7), box2));
assertFalse('SW no overlap',
boxOverlaps(new goog.math.Box(7, 2, 8, 1), box2));
assertFalse('NW no overlap',
boxOverlaps(new goog.math.Box(1, 2, 2, 1), box2));
// Perpendicular to an edge.
assertFalse('NNE no overlap',
boxOverlaps(new goog.math.Box(1, 7, 2, 5), box2));
assertFalse('NEE no overlap',
boxOverlaps(new goog.math.Box(2, 8, 4, 7), box2));
assertFalse('SEE no overlap',
boxOverlaps(new goog.math.Box(5, 8, 7, 7), box2));
assertFalse('SSE no overlap',
boxOverlaps(new goog.math.Box(7, 7, 8, 5), box2));
assertFalse('SSW no overlap',
boxOverlaps(new goog.math.Box(7, 4, 8, 2), box2));
assertFalse('SWW no overlap',
boxOverlaps(new goog.math.Box(5, 2, 7, 1), box2));
assertFalse('NWW no overlap',
boxOverlaps(new goog.math.Box(2, 2, 4, 1), box2));
assertFalse('NNW no overlap',
boxOverlaps(new goog.math.Box(1, 4, 2, 2), box2));
// Along the edge - longer.
assertFalse('N no overlap',
boxOverlaps(new goog.math.Box(0, 7, 1, 2), box2));
assertFalse('E no overlap',
boxOverlaps(new goog.math.Box(2, 9, 7, 8), box2));
assertFalse('S no overlap',
boxOverlaps(new goog.math.Box(8, 7, 9, 2), box2));
assertFalse('W no overlap',
boxOverlaps(new goog.math.Box(2, 1, 7, 0), box2));
}
/**
* Checks whether a given box overlaps any of given DnD target boxes.
*
* @param {goog.math.Box} box The box to check.
* @param {Array<Object>} targets The array of targets with boxes to check
* if they overlap with the given box.
* @return {boolean} Whether the box overlaps any of the target boxes.
*/
function boxOverlapsTargets(box, targets) {
return goog.array.some(targets, function(target) {
return boxOverlaps(box, target.box_);
});
}
function testMaybeCreateDummyTargetForPosition() {
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = targets;
testGroup.targetBox_ = new goog.math.Box(0, 9, 10, 1);
var target = testGroup.maybeCreateDummyTargetForPosition_(3, 3);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(3, 3, target.box_));
target = testGroup.maybeCreateDummyTargetForPosition_(2, 4);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(2, 4, target.box_));
target = testGroup.maybeCreateDummyTargetForPosition_(2, 7);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(2, 7, target.box_));
testGroup.targetList_.push({ box_: new goog.math.Box(5, 6, 6, 0) });
target = testGroup.maybeCreateDummyTargetForPosition_(3, 3);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(3, 3, target.box_));
target = testGroup.maybeCreateDummyTargetForPosition_(2, 7);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(2, 7, target.box_));
target = testGroup.maybeCreateDummyTargetForPosition_(6, 3);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(6, 3, target.box_));
target = testGroup.maybeCreateDummyTargetForPosition_(0, 3);
assertNull(target);
target = testGroup.maybeCreateDummyTargetForPosition_(9, 0);
assertNull(target);
}
function testMaybeCreateDummyTargetForPosition2() {
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = targets2;
testGroup.targetBox_ = new goog.math.Box(10, 50, 80, 10);
var target = testGroup.maybeCreateDummyTargetForPosition_(30, 40);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(30, 40, target.box_));
target = testGroup.maybeCreateDummyTargetForPosition_(45, 40);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(45, 40, target.box_));
testGroup.targetList_.push({ box_: new goog.math.Box(40, 50, 50, 40) });
target = testGroup.maybeCreateDummyTargetForPosition_(30, 40);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
target = testGroup.maybeCreateDummyTargetForPosition_(45, 35);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
}
function testMaybeCreateDummyTargetForPosition3BoxHasDecentSize() {
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = targets3;
testGroup.targetBox_ = new goog.math.Box(0, 6, 6, 0);
var target = testGroup.maybeCreateDummyTargetForPosition_(3, 3);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(3, 3, target.box_));
assertEquals('(1t, 5r, 5b, 1l)', target.box_.toString());
}
function testMaybeCreateDummyTargetForPosition4() {
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = targets;
testGroup.targetBox_ = new goog.math.Box(0, 9, 10, 1);
for (var x = testGroup.targetBox_.left;
x < testGroup.targetBox_.right;
x++) {
for (var y = testGroup.targetBox_.top;
y < testGroup.targetBox_.bottom;
y++) {
var inRealTarget = false;
for (var i = 0; i < testGroup.targetList_.length; i++) {
if (testGroup.isInside(x, y, testGroup.targetList_[i].box_)) {
inRealTarget = true;
break;
}
}
if (!inRealTarget) {
var target = testGroup.maybeCreateDummyTargetForPosition_(x, y);
if (target) {
assertFalse('Fake target for point(' + x + ',' + y + ') should ' +
'not overlap any real targets.',
boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(x, y, target.box_));
}
}
}
}
}
function testMaybeCreateDummyTargetForPosition_NegativePositions() {
var negTargets = [
{ box_: new goog.math.Box(-20, 10, -5, 1) },
{ box_: new goog.math.Box(20, 10, 30, 1) }
];
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = negTargets;
testGroup.targetBox_ = new goog.math.Box(-20, 10, 30, 1);
var target = testGroup.maybeCreateDummyTargetForPosition_(1, 5);
assertFalse(boxOverlapsTargets(target.box_, testGroup.targetList_));
assertTrue(testGroup.isInside(1, 5, target.box_));
}
function testMaybeCreateDummyTargetOutsideScrollableContainer() {
var targets = [
{ box_: new goog.math.Box(0, 3, 10, 1) },
{ box_: new goog.math.Box(20, 3, 30, 1) }
];
var target = targets[0];
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = targets;
testGroup.targetBox_ = new goog.math.Box(0, 3, 30, 1);
testGroup.addScrollableContainer(document.getElementById('container1'));
var container = testGroup.scrollableContainers_[0];
container.containedTargets_.push(target);
container.box_ = new goog.math.Box(0, 3, 5, 1); // shorter than target
target.scrollableContainer_ = container;
// mouse cursor is below scrollable target but not the actual target
var dummyTarget = testGroup.maybeCreateDummyTargetForPosition_(2, 7);
// dummy target should not overlap the scrollable container
assertFalse(boxOverlaps(dummyTarget.box_, container.box_));
// but should overlap the actual target, since not all of it is visible
assertTrue(boxOverlaps(dummyTarget.box_, target.box_));
}
function testMaybeCreateDummyTargetInsideScrollableContainer() {
var targets = [
{ box_: new goog.math.Box(0, 3, 10, 1) },
{ box_: new goog.math.Box(20, 3, 30, 1) }
];
var target = targets[0];
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = targets;
testGroup.targetBox_ = new goog.math.Box(0, 3, 30, 1);
testGroup.addScrollableContainer(document.getElementById('container1'));
var container = testGroup.scrollableContainers_[0];
container.containedTargets_.push(target);
container.box_ = new goog.math.Box(0, 3, 20, 1); // longer than target
target.scrollableContainer_ = container;
// mouse cursor is below both the scrollable and the actual target
var dummyTarget = testGroup.maybeCreateDummyTargetForPosition_(2, 15);
// dummy target should overlap the scrollable container
assertTrue(boxOverlaps(dummyTarget.box_, container.box_));
// but not overlap the actual target
assertFalse(boxOverlaps(dummyTarget.box_, target.box_));
}
function testCalculateTargetBox() {
var testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = [];
goog.array.forEach(targets, function(target) {
testGroup.targetList_.push(target);
testGroup.calculateTargetBox_(target.box_);
});
assertTrue(goog.math.Box.equals(testGroup.targetBox_,
new goog.math.Box(0, 9, 10, 1)));
testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = [];
goog.array.forEach(targets2, function(target) {
testGroup.targetList_.push(target);
testGroup.calculateTargetBox_(target.box_);
});
assertTrue(goog.math.Box.equals(testGroup.targetBox_,
new goog.math.Box(10, 50, 80, 10)));
testGroup = new goog.fx.AbstractDragDrop();
testGroup.targetList_ = [];
goog.array.forEach(targets3, function(target) {
testGroup.targetList_.push(target);
testGroup.calculateTargetBox_(target.box_);
});
assertTrue(goog.math.Box.equals(testGroup.targetBox_,
new goog.math.Box(0, 6, 6, 0)));
}
function testIsInside() {
var add = new goog.fx.AbstractDragDrop();
// The box in question.
// 10,20+++++20,20
// + |
// 10,30-----20,30
var box = new goog.math.Box(20, 20, 30, 10);
assertTrue('A point somewhere in the middle of the box should be inside.',
add.isInside(15, 25, box));
assertTrue('A point in top-left corner should be inside the box.',
add.isInside(10, 20, box));
assertTrue('A point on top border should be inside the box.',
add.isInside(15, 20, box));
assertFalse('A point in top-right corner should be outside the box.',
add.isInside(20, 20, box));
assertFalse('A point on right border should be outside the box.',
add.isInside(20, 25, box));
assertFalse('A point in bottom-right corner should be outside the box.',
add.isInside(20, 30, box));
assertFalse('A point on bottom border should be outside the box.',
add.isInside(15, 30, box));
assertFalse('A point in bottom-left corner should be outside the box.',
add.isInside(10, 30, box));
assertTrue('A point on left border should be inside the box.',
add.isInside(10, 25, box));
add.dispose();
}
function testAddingRemovingScrollableContainers() {
var group = new goog.fx.AbstractDragDrop();
var el1 = document.createElement('div');
var el2 = document.createElement('div');
assertEquals(0, group.scrollableContainers_.length);
group.addScrollableContainer(el1);
assertEquals(1, group.scrollableContainers_.length);
group.addScrollableContainer(el2);
assertEquals(2, group.scrollableContainers_.length);
group.removeAllScrollableContainers();
assertEquals(0, group.scrollableContainers_.length);
}
function testScrollableContainersCalculation() {
var group = new goog.fx.AbstractDragDrop();
var target = new goog.fx.AbstractDragDrop();
group.addTarget(target);
group.addScrollableContainer(document.getElementById('container1'));
var container = group.scrollableContainers_[0];
var item1 = new goog.fx.DragDropItem(document.getElementById('child1'));
var item2 = new goog.fx.DragDropItem(document.getElementById('child2'));
target.items_.push(item1);
group.recalculateDragTargets();
group.recalculateScrollableContainers();
assertEquals(1, container.containedTargets_.length);
assertEquals(container, group.targetList_[0].scrollableContainer_);
target.items_.push(item2);
group.recalculateDragTargets();
assertEquals(1, container.containedTargets_.length);
assertNull(group.targetList_[0].scrollableContainer_);
group.recalculateScrollableContainers();
assertEquals(2, container.containedTargets_.length);
assertEquals(container, group.targetList_[1].scrollableContainer_);
}
// See http://b/7494613.
function testMouseUpOutsideElement() {
var group = new goog.fx.AbstractDragDrop();
var target = new goog.fx.AbstractDragDrop();
group.addTarget(target);
var item1 = new goog.fx.DragDropItem(document.getElementById('child1'));
group.items_.push(item1);
item1.setParent(group);
group.init();
group.startDrag = goog.functions.error('startDrag should not be called.');
goog.testing.events.fireMouseDownEvent(item1.element);
goog.testing.events.fireMouseUpEvent(item1.element.parentNode);
// This should have no effect (not start a drag) since the previous event
// should have cleared the listeners.
goog.testing.events.fireMouseOutEvent(item1.element);
group.dispose();
target.dispose();
}
function testScrollBeforeMoveDrag() {
var group = new goog.fx.AbstractDragDrop();
var target = new goog.fx.AbstractDragDrop();
group.addTarget(target);
var container = document.getElementById('container1');
group.addScrollableContainer(container);
var childEl = document.getElementById('child1');
var item = new goog.fx.DragDropItem(childEl);
item.currentDragElement_ = childEl;
target.items_.push(item);
group.recalculateDragTargets();
group.recalculateScrollableContainers();
// Simulare starting a drag.
var moveEvent = {
'clientX': 8,
'clientY': 10,
'type': goog.events.EventType.MOUSEMOVE,
'relatedTarget': childEl,
'preventDefault': function() {}
};
group.startDrag(moveEvent, item);
// Simulate scrolling before the first move drag event.
var scrollEvent = {
'target': container
};
assertNotThrows(goog.bind(group.containerScrollHandler_, group, scrollEvent));
}
function testMouseMove_mouseOutBeforeThreshold() {
// Setup dragdrop and item
var itemEl = document.createElement('div');
var childEl = document.createElement('div');
itemEl.appendChild(childEl);
var add = new goog.fx.AbstractDragDrop();
var item = new goog.fx.DragDropItem(itemEl);
item.setParent(add);
add.items_.push(item);
// Simulate maybeStartDrag
item.startPosition_ = new goog.math.Coordinate(10, 10);
item.currentDragElement_ = itemEl;
// Test
var draggedItem = null;
add.startDrag = function(event, item) {
draggedItem = item;
};
var event = {'clientX': 8, 'clientY': 10, // Drag distance is only 2
'type': goog.events.EventType.MOUSEOUT, 'target': childEl};
item.mouseMove_(event);
assertEquals('DragStart should not be fired for mouseout on child element.',
null, draggedItem);
var event = {'clientX': 8, 'clientY': 10, // Drag distance is only 2
'type': goog.events.EventType.MOUSEOUT, 'target': itemEl};
item.mouseMove_(event);
assertEquals('DragStart should be fired for mouseout on main element.',
item, draggedItem);
}
function testGetDragElementPosition() {
var testGroup = new goog.fx.AbstractDragDrop();
var sourceEl = document.createElement('div');
document.body.appendChild(sourceEl);
var pageOffset = goog.style.getPageOffset(sourceEl);
var pos = testGroup.getDragElementPosition(sourceEl);
assertEquals('Drag element position should be source element page offset',
pageOffset.x, pos.x);
assertEquals('Drag element position should be source element page offset',
pageOffset.y, pos.y);
sourceEl.style.marginLeft = '5px';
sourceEl.style.marginTop = '7px';
pageOffset = goog.style.getPageOffset(sourceEl);
pos = testGroup.getDragElementPosition(sourceEl);
assertEquals('Drag element position should be adjusted for source element ' +
'margins', pageOffset.x - 10, pos.x);
assertEquals('Drag element position should be adjusted for source element ' +
'margins', pageOffset.y - 14, pos.y);
}
// Helper function for manual debugging.
function drawTargets(targets, multiplier) {
var colors = ['green', 'blue', 'red', 'lime', 'pink', 'silver', 'orange'];
var cont = document.getElementById('cont');
cont.innerHTML = '';
for (var i = 0; i < targets.length; i++) {
var box = targets[i].box_;
var el = document.createElement('div');
el.style.top = (box.top * multiplier) + 'px';
el.style.left = (box.left * multiplier) + 'px';
el.style.width = ((box.right - box.left) * multiplier) + 'px';
el.style.height = ((box.bottom - box.top) * multiplier) + 'px';
el.style.backgroundColor = colors[i];
cont.appendChild(el);
}
}
@@ -0,0 +1,211 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Basic animation controls.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.fx.anim');
goog.provide('goog.fx.anim.Animated');
goog.require('goog.async.AnimationDelay');
goog.require('goog.async.Delay');
goog.require('goog.object');
/**
* An interface for programatically animated objects. I.e. rendered in
* javascript frame by frame.
*
* @interface
*/
goog.fx.anim.Animated = function() {};
/**
* Function called when a frame is requested for the animation.
*
* @param {number} now Current time in milliseconds.
*/
goog.fx.anim.Animated.prototype.onAnimationFrame;
/**
* Default wait timeout for animations (in milliseconds). Only used for timed
* animation, which uses a timer (setTimeout) to schedule animation.
*
* @type {number}
* @const
*/
goog.fx.anim.TIMEOUT = goog.async.AnimationDelay.TIMEOUT;
/**
* A map of animations which should be cycled on the global timer.
*
* @type {Object<number, goog.fx.anim.Animated>}
* @private
*/
goog.fx.anim.activeAnimations_ = {};
/**
* An optional animation window.
* @type {Window}
* @private
*/
goog.fx.anim.animationWindow_ = null;
/**
* An interval ID for the global timer or event handler uid.
* @type {goog.async.Delay|goog.async.AnimationDelay}
* @private
*/
goog.fx.anim.animationDelay_ = null;
/**
* Registers an animation to be cycled on the global timer.
* @param {goog.fx.anim.Animated} animation The animation to register.
*/
goog.fx.anim.registerAnimation = function(animation) {
var uid = goog.getUid(animation);
if (!(uid in goog.fx.anim.activeAnimations_)) {
goog.fx.anim.activeAnimations_[uid] = animation;
}
// If the timer is not already started, start it now.
goog.fx.anim.requestAnimationFrame_();
};
/**
* Removes an animation from the list of animations which are cycled on the
* global timer.
* @param {goog.fx.anim.Animated} animation The animation to unregister.
*/
goog.fx.anim.unregisterAnimation = function(animation) {
var uid = goog.getUid(animation);
delete goog.fx.anim.activeAnimations_[uid];
// If a timer is running and we no longer have any active timers we stop the
// timers.
if (goog.object.isEmpty(goog.fx.anim.activeAnimations_)) {
goog.fx.anim.cancelAnimationFrame_();
}
};
/**
* Tears down this module. Useful for testing.
*/
// TODO(nicksantos): Wow, this api is pretty broken. This should be fixed.
goog.fx.anim.tearDown = function() {
goog.fx.anim.animationWindow_ = null;
goog.dispose(goog.fx.anim.animationDelay_);
goog.fx.anim.animationDelay_ = null;
goog.fx.anim.activeAnimations_ = {};
};
/**
* Registers an animation window. This allows usage of the timing control API
* for animations. Note that this window must be visible, as non-visible
* windows can potentially stop animating. This window does not necessarily
* need to be the window inside which animation occurs, but must remain visible.
* See: https://developer.mozilla.org/en/DOM/window.mozRequestAnimationFrame.
*
* @param {Window} animationWindow The window in which to animate elements.
*/
goog.fx.anim.setAnimationWindow = function(animationWindow) {
// If a timer is currently running, reset it and restart with new functions
// after a timeout. This is to avoid mismatching timer UIDs if we change the
// animation window during a running animation.
//
// In practice this cannot happen before some animation window and timer
// control functions has already been set.
var hasTimer =
goog.fx.anim.animationDelay_ && goog.fx.anim.animationDelay_.isActive();
goog.dispose(goog.fx.anim.animationDelay_);
goog.fx.anim.animationDelay_ = null;
goog.fx.anim.animationWindow_ = animationWindow;
// If the timer was running, start it again.
if (hasTimer) {
goog.fx.anim.requestAnimationFrame_();
}
};
/**
* Requests an animation frame based on the requestAnimationFrame and
* cancelRequestAnimationFrame function pair.
* @private
*/
goog.fx.anim.requestAnimationFrame_ = function() {
if (!goog.fx.anim.animationDelay_) {
// We cannot guarantee that the global window will be one that fires
// requestAnimationFrame events (consider off-screen chrome extension
// windows). Default to use goog.async.Delay, unless
// the client has explicitly set an animation window.
if (goog.fx.anim.animationWindow_) {
// requestAnimationFrame will call cycleAnimations_ with the current
// time in ms, as returned from goog.now().
goog.fx.anim.animationDelay_ = new goog.async.AnimationDelay(
function(now) {
goog.fx.anim.cycleAnimations_(now);
}, goog.fx.anim.animationWindow_);
} else {
goog.fx.anim.animationDelay_ = new goog.async.Delay(function() {
goog.fx.anim.cycleAnimations_(goog.now());
}, goog.fx.anim.TIMEOUT);
}
}
var delay = goog.fx.anim.animationDelay_;
if (!delay.isActive()) {
delay.start();
}
};
/**
* Cancels an animation frame created by requestAnimationFrame_().
* @private
*/
goog.fx.anim.cancelAnimationFrame_ = function() {
if (goog.fx.anim.animationDelay_) {
goog.fx.anim.animationDelay_.stop();
}
};
/**
* Cycles through all registered animations.
* @param {number} now Current time in milliseconds.
* @private
*/
goog.fx.anim.cycleAnimations_ = function(now) {
goog.object.forEach(goog.fx.anim.activeAnimations_, function(anim) {
anim.onAnimationFrame(now);
});
if (!goog.object.isEmpty(goog.fx.anim.activeAnimations_)) {
goog.fx.anim.requestAnimationFrame_();
}
};
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: arv@google.com (Erik Arvidsson)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.fx.animTest');
</script>
<style>
</style>
</head>
<body>
</body>
</html>
@@ -0,0 +1,218 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.animTest');
goog.setTestOnly('goog.fx.animTest');
goog.require('goog.async.AnimationDelay');
goog.require('goog.async.Delay');
goog.require('goog.events');
goog.require('goog.functions');
goog.require('goog.fx.Animation');
goog.require('goog.fx.anim');
goog.require('goog.object');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
goog.require('goog.userAgent');
var clock, replacer;
function setUpPage() {
clock = new goog.testing.MockClock(true);
}
function tearDownPage() {
clock.dispose();
}
function setUp() {
replacer = new goog.testing.PropertyReplacer();
}
function tearDown() {
replacer.reset();
goog.fx.anim.tearDown();
}
function testDelayWithMocks() {
goog.fx.anim.setAnimationWindow(null);
registerAndUnregisterAnimationWithMocks(goog.async.Delay);
}
function testAnimationDelayWithMocks() {
goog.fx.anim.setAnimationWindow(window);
registerAndUnregisterAnimationWithMocks(goog.async.AnimationDelay);
}
/**
* @param {!Function} delayType The constructor for Delay or AnimationDelay.
* The methods will be mocked out.
*/
function registerAndUnregisterAnimationWithMocks(delayType) {
var timerCount = 0;
replacer.set(delayType.prototype, 'start', function() {
timerCount++;
});
replacer.set(delayType.prototype, 'stop', function() {
timerCount--;
});
replacer.set(delayType.prototype, 'isActive', function() {
return timerCount > 0;
});
var forbiddenDelayType = delayType == goog.async.AnimationDelay ?
goog.async.Delay : goog.async.AnimationDelay;
replacer.set(forbiddenDelayType.prototype,
'start', goog.functions.error());
replacer.set(forbiddenDelayType.prototype,
'stop', goog.functions.error());
replacer.set(forbiddenDelayType.prototype,
'isActive', goog.functions.error());
var anim = new goog.fx.Animation([0], [1], 1000);
var anim2 = new goog.fx.Animation([0], [1], 1000);
goog.fx.anim.registerAnimation(anim);
assertTrue('Should contain the animation',
goog.object.containsValue(goog.fx.anim.activeAnimations_,
anim));
assertEquals('Should have called start once', 1, timerCount);
goog.fx.anim.registerAnimation(anim2);
assertEquals('Should not have called start again', 1, timerCount);
// Add anim again.
goog.fx.anim.registerAnimation(anim);
assertTrue('Should contain the animation',
goog.object.containsValue(goog.fx.anim.activeAnimations_,
anim));
assertEquals('Should not have called start again', 1, timerCount);
goog.fx.anim.unregisterAnimation(anim);
assertFalse('Should not contain the animation',
goog.object.containsValue(goog.fx.anim.activeAnimations_,
anim));
assertEquals('clearTimeout should not have been called', 1, timerCount);
goog.fx.anim.unregisterAnimation(anim2);
assertEquals('There should be no remaining timers', 0, timerCount);
// Make sure we don't trigger setTimeout or setInterval.
clock.tick(1000);
goog.fx.anim.cycleAnimations_(goog.now());
assertEquals('There should be no remaining timers', 0, timerCount);
anim.dispose();
anim2.dispose();
}
function testRegisterAndUnregisterAnimationWithRequestAnimationFrameGecko() {
// Only FF4 onwards support requestAnimationFrame.
if (!goog.userAgent.GECKO || !goog.userAgent.isVersionOrHigher('2.0') ||
goog.userAgent.isVersionOrHigher('17')) {
return;
}
goog.fx.anim.setAnimationWindow(window);
var anim = new goog.fx.Animation([0], [1], 1000);
var anim2 = new goog.fx.Animation([0], [1], 1000);
goog.fx.anim.registerAnimation(anim);
assertTrue('Should contain the animation',
goog.object.containsValue(goog.fx.anim.activeAnimations_,
anim));
assertEquals('Should have listen to MozBeforePaint once', 1,
goog.events.getListeners(window, 'MozBeforePaint', false).length);
goog.fx.anim.registerAnimation(anim2);
assertEquals('Should not add more listener for MozBeforePaint', 1,
goog.events.getListeners(window, 'MozBeforePaint', false).length);
// Add anim again.
goog.fx.anim.registerAnimation(anim);
assertTrue('Should contain the animation',
goog.object.containsValue(goog.fx.anim.activeAnimations_,
anim));
assertEquals('Should not add more listener for MozBeforePaint', 1,
goog.events.getListeners(window, 'MozBeforePaint', false).length);
goog.fx.anim.unregisterAnimation(anim);
assertFalse('Should not contain the animation',
goog.object.containsValue(goog.fx.anim.activeAnimations_,
anim));
assertEquals('Should not clear listener for MozBeforePaint yet', 1,
goog.events.getListeners(window, 'MozBeforePaint', false).length);
goog.fx.anim.unregisterAnimation(anim2);
assertEquals('There should be no more listener for MozBeforePaint', 0,
goog.events.getListeners(window, 'MozBeforePaint', false).length);
anim.dispose();
anim2.dispose();
goog.fx.anim.setAnimationWindow(null);
}
function testRegisterUnregisterAnimation() {
var anim = new goog.fx.Animation([0], [1], 1000);
goog.fx.anim.registerAnimation(anim);
assertTrue('There should be an active timer',
goog.fx.anim.animationDelay_ && goog.fx.anim.animationDelay_.isActive());
assertEquals('There should be an active animations',
1, goog.object.getCount(goog.fx.anim.activeAnimations_));
goog.fx.anim.unregisterAnimation(anim);
assertTrue('There should be no active animations',
goog.object.isEmpty(goog.fx.anim.activeAnimations_));
assertFalse('There should be no active timer',
goog.fx.anim.animationDelay_ && goog.fx.anim.animationDelay_.isActive());
anim.dispose();
}
function testCycleWithMockClock() {
goog.fx.anim.setAnimationWindow(null);
var anim = new goog.fx.Animation([0], [1], 1000);
anim.onAnimationFrame = goog.testing.recordFunction();
goog.fx.anim.registerAnimation(anim);
clock.tick(goog.fx.anim.TIMEOUT);
assertEquals(1, anim.onAnimationFrame.getCallCount());
}
function testCycleWithMockClockAndAnimationWindow() {
goog.fx.anim.setAnimationWindow(window);
var anim = new goog.fx.Animation([0], [1], 1000);
anim.onAnimationFrame = goog.testing.recordFunction();
goog.fx.anim.registerAnimation(anim);
clock.tick(goog.fx.anim.TIMEOUT);
assertEquals(1, anim.onAnimationFrame.getCallCount());
}
@@ -0,0 +1,524 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Classes for doing animations and visual effects.
*
* (Based loosly on my animation code for 13thparallel.org, with extra
* inspiration from the DojoToolkit's modifications to my code)
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.fx.Animation');
goog.provide('goog.fx.Animation.EventType');
goog.provide('goog.fx.Animation.State');
goog.provide('goog.fx.AnimationEvent');
goog.require('goog.array');
goog.require('goog.events.Event');
goog.require('goog.fx.Transition'); // Unreferenced: interface
goog.require('goog.fx.TransitionBase');
goog.require('goog.fx.anim');
goog.require('goog.fx.anim.Animated'); // Unreferenced: interface
/**
* Constructor for an animation object.
* @param {Array<number>} start Array for start coordinates.
* @param {Array<number>} end Array for end coordinates.
* @param {number} duration Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @constructor
* @implements {goog.fx.anim.Animated}
* @implements {goog.fx.Transition}
* @extends {goog.fx.TransitionBase}
*/
goog.fx.Animation = function(start, end, duration, opt_acc) {
goog.fx.Animation.base(this, 'constructor');
if (!goog.isArray(start) || !goog.isArray(end)) {
throw Error('Start and end parameters must be arrays');
}
if (start.length != end.length) {
throw Error('Start and end points must be the same length');
}
/**
* Start point.
* @type {Array<number>}
* @protected
*/
this.startPoint = start;
/**
* End point.
* @type {Array<number>}
* @protected
*/
this.endPoint = end;
/**
* Duration of animation in milliseconds.
* @type {number}
* @protected
*/
this.duration = duration;
/**
* Acceleration function, which must return a number between 0 and 1 for
* inputs between 0 and 1.
* @type {Function|undefined}
* @private
*/
this.accel_ = opt_acc;
/**
* Current coordinate for animation.
* @type {Array<number>}
* @protected
*/
this.coords = [];
/**
* Whether the animation should use "right" rather than "left" to position
* elements in RTL. This is a temporary flag to allow clients to transition
* to the new behavior at their convenience. At some point it will be the
* default.
* @type {boolean}
* @private
*/
this.useRightPositioningForRtl_ = false;
/**
* Current frame rate.
* @private {number}
*/
this.fps_ = 0;
/**
* Percent of the way through the animation.
* @protected {number}
*/
this.progress = 0;
/**
* Timestamp for when last frame was run.
* @protected {?number}
*/
this.lastFrame = null;
};
goog.inherits(goog.fx.Animation, goog.fx.TransitionBase);
/**
* Sets whether the animation should use "right" rather than "left" to position
* elements. This is a temporary flag to allow clients to transition
* to the new component at their convenience. At some point "right" will be
* used for RTL elements by default.
* @param {boolean} useRightPositioningForRtl True if "right" should be used for
* positioning, false if "left" should be used for positioning.
*/
goog.fx.Animation.prototype.enableRightPositioningForRtl =
function(useRightPositioningForRtl) {
this.useRightPositioningForRtl_ = useRightPositioningForRtl;
};
/**
* Whether the animation should use "right" rather than "left" to position
* elements. This is a temporary flag to allow clients to transition
* to the new component at their convenience. At some point "right" will be
* used for RTL elements by default.
* @return {boolean} True if "right" should be used for positioning, false if
* "left" should be used for positioning.
*/
goog.fx.Animation.prototype.isRightPositioningForRtlEnabled = function() {
return this.useRightPositioningForRtl_;
};
/**
* Events fired by the animation.
* @enum {string}
*/
goog.fx.Animation.EventType = {
/**
* Dispatched when played for the first time OR when it is resumed.
* @deprecated Use goog.fx.Transition.EventType.PLAY.
*/
PLAY: goog.fx.Transition.EventType.PLAY,
/**
* Dispatched only when the animation starts from the beginning.
* @deprecated Use goog.fx.Transition.EventType.BEGIN.
*/
BEGIN: goog.fx.Transition.EventType.BEGIN,
/**
* Dispatched only when animation is restarted after a pause.
* @deprecated Use goog.fx.Transition.EventType.RESUME.
*/
RESUME: goog.fx.Transition.EventType.RESUME,
/**
* Dispatched when animation comes to the end of its duration OR stop
* is called.
* @deprecated Use goog.fx.Transition.EventType.END.
*/
END: goog.fx.Transition.EventType.END,
/**
* Dispatched only when stop is called.
* @deprecated Use goog.fx.Transition.EventType.STOP.
*/
STOP: goog.fx.Transition.EventType.STOP,
/**
* Dispatched only when animation comes to its end naturally.
* @deprecated Use goog.fx.Transition.EventType.FINISH.
*/
FINISH: goog.fx.Transition.EventType.FINISH,
/**
* Dispatched when an animation is paused.
* @deprecated Use goog.fx.Transition.EventType.PAUSE.
*/
PAUSE: goog.fx.Transition.EventType.PAUSE,
/**
* Dispatched each frame of the animation. This is where the actual animator
* will listen.
*/
ANIMATE: 'animate',
/**
* Dispatched when the animation is destroyed.
*/
DESTROY: 'destroy'
};
/**
* @deprecated Use goog.fx.anim.TIMEOUT.
*/
goog.fx.Animation.TIMEOUT = goog.fx.anim.TIMEOUT;
/**
* Enum for the possible states of an animation.
* @deprecated Use goog.fx.Transition.State instead.
* @enum {number}
*/
goog.fx.Animation.State = goog.fx.TransitionBase.State;
/**
* @deprecated Use goog.fx.anim.setAnimationWindow.
* @param {Window} animationWindow The window in which to animate elements.
*/
goog.fx.Animation.setAnimationWindow = function(animationWindow) {
goog.fx.anim.setAnimationWindow(animationWindow);
};
/**
* Starts or resumes an animation.
* @param {boolean=} opt_restart Whether to restart the
* animation from the beginning if it has been paused.
* @return {boolean} Whether animation was started.
* @override
*/
goog.fx.Animation.prototype.play = function(opt_restart) {
if (opt_restart || this.isStopped()) {
this.progress = 0;
this.coords = this.startPoint;
} else if (this.isPlaying()) {
return false;
}
goog.fx.anim.unregisterAnimation(this);
var now = /** @type {number} */ (goog.now());
this.startTime = now;
if (this.isPaused()) {
this.startTime -= this.duration * this.progress;
}
this.endTime = this.startTime + this.duration;
this.lastFrame = this.startTime;
if (!this.progress) {
this.onBegin();
}
this.onPlay();
if (this.isPaused()) {
this.onResume();
}
this.setStatePlaying();
goog.fx.anim.registerAnimation(this);
this.cycle(now);
return true;
};
/**
* Stops the animation.
* @param {boolean=} opt_gotoEnd If true the animation will move to the
* end coords.
* @override
*/
goog.fx.Animation.prototype.stop = function(opt_gotoEnd) {
goog.fx.anim.unregisterAnimation(this);
this.setStateStopped();
if (!!opt_gotoEnd) {
this.progress = 1;
}
this.updateCoords_(this.progress);
this.onStop();
this.onEnd();
};
/**
* Pauses the animation (iff it's playing).
* @override
*/
goog.fx.Animation.prototype.pause = function() {
if (this.isPlaying()) {
goog.fx.anim.unregisterAnimation(this);
this.setStatePaused();
this.onPause();
}
};
/**
* @return {number} The current progress of the animation, the number
* is between 0 and 1 inclusive.
*/
goog.fx.Animation.prototype.getProgress = function() {
return this.progress;
};
/**
* Sets the progress of the animation.
* @param {number} progress The new progress of the animation.
*/
goog.fx.Animation.prototype.setProgress = function(progress) {
this.progress = progress;
if (this.isPlaying()) {
var now = goog.now();
// If the animation is already playing, we recompute startTime and endTime
// such that the animation plays consistently, that is:
// now = startTime + progress * duration.
this.startTime = now - this.duration * this.progress;
this.endTime = this.startTime + this.duration;
}
};
/**
* Disposes of the animation. Stops an animation, fires a 'destroy' event and
* then removes all the event handlers to clean up memory.
* @override
* @protected
*/
goog.fx.Animation.prototype.disposeInternal = function() {
if (!this.isStopped()) {
this.stop(false);
}
this.onDestroy();
goog.fx.Animation.base(this, 'disposeInternal');
};
/**
* Stops an animation, fires a 'destroy' event and then removes all the event
* handlers to clean up memory.
* @deprecated Use dispose() instead.
*/
goog.fx.Animation.prototype.destroy = function() {
this.dispose();
};
/** @override */
goog.fx.Animation.prototype.onAnimationFrame = function(now) {
this.cycle(now);
};
/**
* Handles the actual iteration of the animation in a timeout
* @param {number} now The current time.
*/
goog.fx.Animation.prototype.cycle = function(now) {
this.progress = (now - this.startTime) / (this.endTime - this.startTime);
if (this.progress >= 1) {
this.progress = 1;
}
this.fps_ = 1000 / (now - this.lastFrame);
this.lastFrame = now;
this.updateCoords_(this.progress);
// Animation has finished.
if (this.progress == 1) {
this.setStateStopped();
goog.fx.anim.unregisterAnimation(this);
this.onFinish();
this.onEnd();
// Animation is still under way.
} else if (this.isPlaying()) {
this.onAnimate();
}
};
/**
* Calculates current coordinates, based on the current state. Applies
* the accelleration function if it exists.
* @param {number} t Percentage of the way through the animation as a decimal.
* @private
*/
goog.fx.Animation.prototype.updateCoords_ = function(t) {
if (goog.isFunction(this.accel_)) {
t = this.accel_(t);
}
this.coords = new Array(this.startPoint.length);
for (var i = 0; i < this.startPoint.length; i++) {
this.coords[i] = (this.endPoint[i] - this.startPoint[i]) * t +
this.startPoint[i];
}
};
/**
* Dispatches the ANIMATE event. Sub classes should override this instead
* of listening to the event.
* @protected
*/
goog.fx.Animation.prototype.onAnimate = function() {
this.dispatchAnimationEvent(goog.fx.Animation.EventType.ANIMATE);
};
/**
* Dispatches the DESTROY event. Sub classes should override this instead
* of listening to the event.
* @protected
*/
goog.fx.Animation.prototype.onDestroy = function() {
this.dispatchAnimationEvent(goog.fx.Animation.EventType.DESTROY);
};
/** @override */
goog.fx.Animation.prototype.dispatchAnimationEvent = function(type) {
this.dispatchEvent(new goog.fx.AnimationEvent(type, this));
};
/**
* Class for an animation event object.
* @param {string} type Event type.
* @param {goog.fx.Animation} anim An animation object.
* @constructor
* @extends {goog.events.Event}
*/
goog.fx.AnimationEvent = function(type, anim) {
goog.fx.AnimationEvent.base(this, 'constructor', type);
/**
* The current coordinates.
* @type {Array<number>}
*/
this.coords = anim.coords;
/**
* The x coordinate.
* @type {number}
*/
this.x = anim.coords[0];
/**
* The y coordinate.
* @type {number}
*/
this.y = anim.coords[1];
/**
* The z coordinate.
* @type {number}
*/
this.z = anim.coords[2];
/**
* The current duration.
* @type {number}
*/
this.duration = anim.duration;
/**
* The current progress.
* @type {number}
*/
this.progress = anim.getProgress();
/**
* Frames per second so far.
*/
this.fps = anim.fps_;
/**
* The state of the animation.
* @type {number}
*/
this.state = anim.getStateInternal();
/**
* The animation object.
* @type {goog.fx.Animation}
*/
// TODO(arv): This can be removed as this is the same as the target
this.anim = anim;
};
goog.inherits(goog.fx.AnimationEvent, goog.events.Event);
/**
* Returns the coordinates as integers (rounded to nearest integer).
* @return {!Array<number>} An array of the coordinates rounded to
* the nearest integer.
*/
goog.fx.AnimationEvent.prototype.coordsAsInts = function() {
return goog.array.map(this.coords, Math.round);
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.Animation
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.AnimationTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,116 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.AnimationTest');
goog.setTestOnly('goog.fx.AnimationTest');
goog.require('goog.events');
goog.require('goog.fx.Animation');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
var clock;
function setUpPage() {
clock = new goog.testing.MockClock(true);
}
function tearDownPage() {
clock.dispose();
}
function testPauseLogic() {
var anim = new goog.fx.Animation([], [], 3000);
var nFrames = 0;
goog.events.listen(anim, goog.fx.Animation.EventType.ANIMATE, function(e) {
assertRoughlyEquals(e.progress, progress, 1e-6);
nFrames++;
});
goog.events.listen(anim, goog.fx.Animation.EventType.END, function(e) {
nFrames++;
});
var nSteps = 10;
for (var i = 0; i < nSteps; i++) {
progress = i / (nSteps - 1);
anim.setProgress(progress);
anim.play();
anim.pause();
}
assertEquals(nSteps, nFrames);
}
function testPauseOffset() {
var anim = new goog.fx.Animation([0], [1000], 1000);
anim.play();
assertEquals(0, anim.coords[0]);
assertRoughlyEquals(0, anim.progress, 1e-4);
clock.tick(300);
assertEquals(300, anim.coords[0]);
assertRoughlyEquals(0.3, anim.progress, 1e-4);
anim.pause();
clock.tick(400);
assertEquals(300, anim.coords[0]);
assertRoughlyEquals(0.3, anim.progress, 1e-4);
anim.play();
assertEquals(300, anim.coords[0]);
assertRoughlyEquals(0.3, anim.progress, 1e-4);
clock.tick(400);
assertEquals(700, anim.coords[0]);
assertRoughlyEquals(0.7, anim.progress, 1e-4);
anim.pause();
clock.tick(300);
assertEquals(700, anim.coords[0]);
assertRoughlyEquals(0.7, anim.progress, 1e-4);
anim.play();
var lastPlay = goog.now();
assertEquals(700, anim.coords[0]);
assertRoughlyEquals(0.7, anim.progress, 1e-4);
clock.tick(300);
assertEquals(1000, anim.coords[0]);
assertRoughlyEquals(1, anim.progress, 1e-4);
assertEquals(goog.fx.Animation.State.STOPPED, anim.getStateInternal());
}
function testSetProgress() {
var anim = new goog.fx.Animation([0], [1000], 3000);
var nFrames = 0;
anim.play();
anim.setProgress(0.5);
goog.events.listen(anim, goog.fx.Animation.EventType.ANIMATE, function(e) {
assertEquals(500, e.coords[0]);
assertRoughlyEquals(0.5, e.progress, 1e-4);
nFrames++;
});
anim.cycle(goog.now());
anim.stop();
assertEquals(1, nFrames);
}
@@ -0,0 +1,310 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A class which automatically plays through a queue of
* animations. AnimationParallelQueue and AnimationSerialQueue provide
* specific implementations of the abstract class AnimationQueue.
*
* @see ../demos/animationqueue.html
*/
goog.provide('goog.fx.AnimationParallelQueue');
goog.provide('goog.fx.AnimationQueue');
goog.provide('goog.fx.AnimationSerialQueue');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.fx.Transition');
goog.require('goog.fx.TransitionBase');
/**
* Constructor for AnimationQueue object.
*
* @constructor
* @extends {goog.fx.TransitionBase}
* @struct
* @suppress {checkStructDictInheritance}
*/
goog.fx.AnimationQueue = function() {
goog.fx.AnimationQueue.base(this, 'constructor');
/**
* An array holding all animations in the queue.
* @type {Array<goog.fx.TransitionBase>}
* @protected
*/
this.queue = [];
};
goog.inherits(goog.fx.AnimationQueue, goog.fx.TransitionBase);
/**
* Pushes an Animation to the end of the queue.
* @param {goog.fx.TransitionBase} animation The animation to add to the queue.
*/
goog.fx.AnimationQueue.prototype.add = function(animation) {
goog.asserts.assert(this.isStopped(),
'Not allowed to add animations to a running animation queue.');
if (goog.array.contains(this.queue, animation)) {
return;
}
this.queue.push(animation);
goog.events.listen(animation, goog.fx.Transition.EventType.FINISH,
this.onAnimationFinish, false, this);
};
/**
* Removes an Animation from the queue.
* @param {goog.fx.Animation} animation The animation to remove.
*/
goog.fx.AnimationQueue.prototype.remove = function(animation) {
goog.asserts.assert(this.isStopped(),
'Not allowed to remove animations from a running animation queue.');
if (goog.array.remove(this.queue, animation)) {
goog.events.unlisten(animation, goog.fx.Transition.EventType.FINISH,
this.onAnimationFinish, false, this);
}
};
/**
* Handles the event that an animation has finished.
* @param {goog.events.Event} e The finishing event.
* @protected
*/
goog.fx.AnimationQueue.prototype.onAnimationFinish = goog.abstractMethod;
/**
* Disposes of the animations.
* @override
*/
goog.fx.AnimationQueue.prototype.disposeInternal = function() {
goog.array.forEach(this.queue, function(animation) {
animation.dispose();
});
this.queue.length = 0;
goog.fx.AnimationQueue.base(this, 'disposeInternal');
};
/**
* Constructor for AnimationParallelQueue object.
* @constructor
* @extends {goog.fx.AnimationQueue}
* @struct
*/
goog.fx.AnimationParallelQueue = function() {
goog.fx.AnimationParallelQueue.base(this, 'constructor');
/**
* Number of finished animations.
* @type {number}
* @private
*/
this.finishedCounter_ = 0;
};
goog.inherits(goog.fx.AnimationParallelQueue, goog.fx.AnimationQueue);
/** @override */
goog.fx.AnimationParallelQueue.prototype.play = function(opt_restart) {
if (this.queue.length == 0) {
return false;
}
if (opt_restart || this.isStopped()) {
this.finishedCounter_ = 0;
this.onBegin();
} else if (this.isPlaying()) {
return false;
}
this.onPlay();
if (this.isPaused()) {
this.onResume();
}
var resuming = this.isPaused() && !opt_restart;
this.startTime = goog.now();
this.endTime = null;
this.setStatePlaying();
goog.array.forEach(this.queue, function(anim) {
if (!resuming || anim.isPaused()) {
anim.play(opt_restart);
}
});
return true;
};
/** @override */
goog.fx.AnimationParallelQueue.prototype.pause = function() {
if (this.isPlaying()) {
goog.array.forEach(this.queue, function(anim) {
if (anim.isPlaying()) {
anim.pause();
}
});
this.setStatePaused();
this.onPause();
}
};
/** @override */
goog.fx.AnimationParallelQueue.prototype.stop = function(opt_gotoEnd) {
goog.array.forEach(this.queue, function(anim) {
if (!anim.isStopped()) {
anim.stop(opt_gotoEnd);
}
});
this.setStateStopped();
this.endTime = goog.now();
this.onStop();
this.onEnd();
};
/** @override */
goog.fx.AnimationParallelQueue.prototype.onAnimationFinish = function(e) {
this.finishedCounter_++;
if (this.finishedCounter_ == this.queue.length) {
this.endTime = goog.now();
this.setStateStopped();
this.onFinish();
this.onEnd();
}
};
/**
* Constructor for AnimationSerialQueue object.
* @constructor
* @extends {goog.fx.AnimationQueue}
* @struct
*/
goog.fx.AnimationSerialQueue = function() {
goog.fx.AnimationSerialQueue.base(this, 'constructor');
/**
* Current animation in queue currently active.
* @type {number}
* @private
*/
this.current_ = 0;
};
goog.inherits(goog.fx.AnimationSerialQueue, goog.fx.AnimationQueue);
/** @override */
goog.fx.AnimationSerialQueue.prototype.play = function(opt_restart) {
if (this.queue.length == 0) {
return false;
}
if (opt_restart || this.isStopped()) {
if (this.current_ < this.queue.length &&
!this.queue[this.current_].isStopped()) {
this.queue[this.current_].stop(false);
}
this.current_ = 0;
this.onBegin();
} else if (this.isPlaying()) {
return false;
}
this.onPlay();
if (this.isPaused()) {
this.onResume();
}
this.startTime = goog.now();
this.endTime = null;
this.setStatePlaying();
this.queue[this.current_].play(opt_restart);
return true;
};
/** @override */
goog.fx.AnimationSerialQueue.prototype.pause = function() {
if (this.isPlaying()) {
this.queue[this.current_].pause();
this.setStatePaused();
this.onPause();
}
};
/** @override */
goog.fx.AnimationSerialQueue.prototype.stop = function(opt_gotoEnd) {
this.setStateStopped();
this.endTime = goog.now();
if (opt_gotoEnd) {
for (var i = this.current_; i < this.queue.length; ++i) {
var anim = this.queue[i];
// If the animation is stopped, start it to initiate rendering. This
// might be needed to make the next line work.
if (anim.isStopped()) anim.play();
// If the animation is not done, stop it and go to the end state of the
// animation.
if (!anim.isStopped()) anim.stop(true);
}
} else if (this.current_ < this.queue.length) {
this.queue[this.current_].stop(false);
}
this.onStop();
this.onEnd();
};
/** @override */
goog.fx.AnimationSerialQueue.prototype.onAnimationFinish = function(e) {
if (this.isPlaying()) {
this.current_++;
if (this.current_ < this.queue.length) {
this.queue[this.current_].play();
} else {
this.endTime = goog.now();
this.setStateStopped();
this.onFinish();
this.onEnd();
}
}
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.Animation
</title>
<script type="text/javascript" src="../base.js">
</script>
<script type="text/javascript">
goog.require('goog.fx.AnimationQueueTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,315 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.AnimationQueueTest');
goog.setTestOnly('goog.fx.AnimationQueueTest');
goog.require('goog.events');
goog.require('goog.fx.Animation');
goog.require('goog.fx.AnimationParallelQueue');
goog.require('goog.fx.AnimationSerialQueue');
goog.require('goog.fx.Transition');
goog.require('goog.fx.anim');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
var clock;
function setUpPage() {
clock = new goog.testing.MockClock(true);
goog.fx.anim.setAnimationWindow(null);
}
function tearDownPage() {
clock.dispose();
}
function testParallelEvents() {
var anim = new goog.fx.AnimationParallelQueue();
anim.add(new goog.fx.Animation([0], [100], 200));
anim.add(new goog.fx.Animation([0], [100], 400));
anim.add(new goog.fx.Animation([0], [100], 600));
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isStopped());
var playEvents = 0, beginEvents = 0, resumeEvents = 0, pauseEvents = 0;
var endEvents = 0, stopEvents = 0, finishEvents = 0;
goog.events.listen(anim, goog.fx.Transition.EventType.PLAY, function() {
++playEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.BEGIN, function() {
++beginEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.RESUME, function() {
++resumeEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.PAUSE, function() {
++pauseEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.END, function() {
++endEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.STOP, function() {
++stopEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.FINISH, function() {
++finishEvents; });
// PLAY, BEGIN
anim.play();
// No queue events.
clock.tick(100);
// PAUSE
anim.pause();
// No queue events
clock.tick(200);
// PLAY, RESUME
anim.play();
// No queue events.
clock.tick(400);
// END, STOP
anim.stop();
// PLAY, BEGIN
anim.play();
// No queue events.
clock.tick(400);
// END, FINISH
clock.tick(200);
// Make sure the event counts are right.
assertEquals(3, playEvents);
assertEquals(2, beginEvents);
assertEquals(1, resumeEvents);
assertEquals(1, pauseEvents);
assertEquals(2, endEvents);
assertEquals(1, stopEvents);
assertEquals(1, finishEvents);
}
function testSerialEvents() {
var anim = new goog.fx.AnimationSerialQueue();
anim.add(new goog.fx.Animation([0], [100], 100));
anim.add(new goog.fx.Animation([0], [100], 200));
anim.add(new goog.fx.Animation([0], [100], 300));
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isStopped());
var playEvents = 0, beginEvents = 0, resumeEvents = 0, pauseEvents = 0;
var endEvents = 0, stopEvents = 0, finishEvents = 0;
goog.events.listen(anim, goog.fx.Transition.EventType.PLAY, function() {
++playEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.BEGIN, function() {
++beginEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.RESUME, function() {
++resumeEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.PAUSE, function() {
++pauseEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.END, function() {
++endEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.STOP, function() {
++stopEvents; });
goog.events.listen(anim, goog.fx.Transition.EventType.FINISH, function() {
++finishEvents; });
// PLAY, BEGIN
anim.play();
// No queue events.
clock.tick(100);
// PAUSE
anim.pause();
// No queue events
clock.tick(200);
// PLAY, RESUME
anim.play();
// No queue events.
clock.tick(400);
// END, STOP
anim.stop();
// PLAY, BEGIN
anim.play(true);
// No queue events.
clock.tick(400);
// END, FINISH
clock.tick(200);
// Make sure the event counts are right.
assertEquals(3, playEvents);
assertEquals(2, beginEvents);
assertEquals(1, resumeEvents);
assertEquals(1, pauseEvents);
assertEquals(2, endEvents);
assertEquals(1, stopEvents);
assertEquals(1, finishEvents);
}
function testParallelPause() {
var anim = new goog.fx.AnimationParallelQueue();
anim.add(new goog.fx.Animation([0], [100], 100));
anim.add(new goog.fx.Animation([0], [100], 200));
anim.add(new goog.fx.Animation([0], [100], 300));
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isStopped());
anim.play();
assertTrue(anim.queue[0].isPlaying());
assertTrue(anim.queue[1].isPlaying());
assertTrue(anim.queue[2].isPlaying());
assertTrue(anim.isPlaying());
clock.tick(100);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPlaying());
assertTrue(anim.queue[2].isPlaying());
assertTrue(anim.isPlaying());
anim.pause();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPaused());
assertTrue(anim.queue[2].isPaused());
assertTrue(anim.isPaused());
clock.tick(200);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPaused());
assertTrue(anim.queue[2].isPaused());
assertTrue(anim.isPaused());
anim.play();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPlaying());
assertTrue(anim.queue[2].isPlaying());
assertTrue(anim.isPlaying());
clock.tick(100);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPlaying());
assertTrue(anim.isPlaying());
anim.pause();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPaused());
assertTrue(anim.isPaused());
clock.tick(200);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPaused());
assertTrue(anim.isPaused());
anim.play();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPlaying());
assertTrue(anim.isPlaying());
clock.tick(100);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isStopped());
}
function testSerialPause() {
var anim = new goog.fx.AnimationSerialQueue();
anim.add(new goog.fx.Animation([0], [100], 100));
anim.add(new goog.fx.Animation([0], [100], 200));
anim.add(new goog.fx.Animation([0], [100], 300));
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isStopped());
anim.play();
assertTrue(anim.queue[0].isPlaying());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isPlaying());
clock.tick(100);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPlaying());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isPlaying());
anim.pause();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPaused());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isPaused());
clock.tick(400);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPaused());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isPaused());
anim.play();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isPlaying());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isPlaying());
clock.tick(200);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPlaying());
assertTrue(anim.isPlaying());
anim.pause();
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPaused());
assertTrue(anim.isPaused());
clock.tick(300);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isPaused());
assertTrue(anim.isPaused());
anim.play();
clock.tick(300);
assertTrue(anim.queue[0].isStopped());
assertTrue(anim.queue[1].isStopped());
assertTrue(anim.queue[2].isStopped());
assertTrue(anim.isStopped());
}
@@ -0,0 +1,63 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A collection of CSS3 targeted animation, based on
* {@code goog.fx.css3.Transition}.
*
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.fx.css3');
goog.require('goog.fx.css3.Transition');
/**
* Creates a transition to fade the element.
* @param {Element} element The element to fade.
* @param {number} duration Duration in seconds.
* @param {string} timing The CSS3 timing function.
* @param {number} startOpacity Starting opacity.
* @param {number} endOpacity Ending opacity.
* @return {!goog.fx.css3.Transition} The transition object.
*/
goog.fx.css3.fade = function(
element, duration, timing, startOpacity, endOpacity) {
return new goog.fx.css3.Transition(
element, duration, {'opacity': startOpacity}, {'opacity': endOpacity},
{property: 'opacity', duration: duration, timing: timing, delay: 0});
};
/**
* Creates a transition to fade in the element.
* @param {Element} element The element to fade in.
* @param {number} duration Duration in seconds.
* @return {!goog.fx.css3.Transition} The transition object.
*/
goog.fx.css3.fadeIn = function(element, duration) {
return goog.fx.css3.fade(element, duration, 'ease-out', 0, 1);
};
/**
* Creates a transition to fade out the element.
* @param {Element} element The element to fade out.
* @param {number} duration Duration in seconds.
* @return {!goog.fx.css3.Transition} The transition object.
*/
goog.fx.css3.fadeOut = function(element, duration) {
return goog.fx.css3.fade(element, duration, 'ease-in', 1, 0);
};
@@ -0,0 +1,201 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview CSS3 transition base library.
*
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.fx.css3.Transition');
goog.require('goog.Timer');
goog.require('goog.asserts');
goog.require('goog.fx.TransitionBase');
goog.require('goog.style');
goog.require('goog.style.transition');
/**
* A class to handle targeted CSS3 transition. This class
* handles common features required for targeted CSS3 transition.
*
* Browser that does not support CSS3 transition will still receive all
* the events fired by the transition object, but will not have any transition
* played. If the browser supports the final state as set in setFinalState
* method, the element will ends in the final state.
*
* Transitioning multiple properties with the same setting is possible
* by setting Css3Property's property to 'all'. Performing multiple
* transitions can be done via setting multiple initialStyle,
* finalStyle and transitions. Css3Property's delay can be used to
* delay one of the transition. Here is an example for a transition
* that expands on the width and then followed by the height:
*
* <pre>
* initialStyle: {width: 10px, height: 10px}
* finalStyle: {width: 100px, height: 100px}
* transitions: [
* {property: width, duration: 1, timing: 'ease-in', delay: 0},
* {property: height, duration: 1, timing: 'ease-in', delay: 1}
* ]
* </pre>
*
* @param {Element} element The element to be transitioned.
* @param {number} duration The duration of the transition in seconds.
* This should be the longest of all transitions.
* @param {Object} initialStyle Initial style properties of the element before
* animating. Set using {@code goog.style.setStyle}.
* @param {Object} finalStyle Final style properties of the element after
* animating. Set using {@code goog.style.setStyle}.
* @param {goog.style.transition.Css3Property|
* Array<goog.style.transition.Css3Property>} transitions A single CSS3
* transition property or an array of it.
* @extends {goog.fx.TransitionBase}
* @constructor
*/
goog.fx.css3.Transition = function(
element, duration, initialStyle, finalStyle, transitions) {
goog.fx.css3.Transition.base(this, 'constructor');
/**
* @type {Element}
* @private
*/
this.element_ = element;
/**
* @type {number}
* @private
*/
this.duration_ = duration;
/**
* @type {Object}
* @private
*/
this.initialStyle_ = initialStyle;
/**
* @type {Object}
* @private
*/
this.finalStyle_ = finalStyle;
/**
* @type {Array<goog.style.transition.Css3Property>}
* @private
*/
this.transitions_ = goog.isArray(transitions) ? transitions : [transitions];
};
goog.inherits(goog.fx.css3.Transition, goog.fx.TransitionBase);
/**
* Timer id to be used to cancel animation part-way.
* @type {number}
* @private
*/
goog.fx.css3.Transition.prototype.timerId_;
/** @override */
goog.fx.css3.Transition.prototype.play = function() {
if (this.isPlaying()) {
return false;
}
this.onBegin();
this.onPlay();
this.startTime = goog.now();
this.setStatePlaying();
if (goog.style.transition.isSupported()) {
goog.style.setStyle(this.element_, this.initialStyle_);
// Allow element to get updated to its initial state before installing
// CSS3 transition.
this.timerId_ = goog.Timer.callOnce(this.play_, undefined, this);
return true;
} else {
this.stop_(false);
return false;
}
};
/**
* Helper method for play method. This needs to be executed on a timer.
* @private
*/
goog.fx.css3.Transition.prototype.play_ = function() {
// This measurement of the DOM element causes the browser to recalculate its
// initial state before the transition starts.
goog.style.getSize(this.element_);
goog.style.transition.set(this.element_, this.transitions_);
goog.style.setStyle(this.element_, this.finalStyle_);
this.timerId_ = goog.Timer.callOnce(
goog.bind(this.stop_, this, false), this.duration_ * 1000);
};
/** @override */
goog.fx.css3.Transition.prototype.stop = function() {
if (!this.isPlaying()) return;
this.stop_(true);
};
/**
* Helper method for stop method.
* @param {boolean} stopped If the transition was stopped.
* @private
*/
goog.fx.css3.Transition.prototype.stop_ = function(stopped) {
goog.style.transition.removeAll(this.element_);
// Clear the timer.
goog.Timer.clear(this.timerId_);
// Make sure that we have reached the final style.
goog.style.setStyle(this.element_, this.finalStyle_);
this.endTime = goog.now();
this.setStateStopped();
if (stopped) {
this.onStop();
} else {
this.onFinish();
}
this.onEnd();
};
/** @override */
goog.fx.css3.Transition.prototype.disposeInternal = function() {
this.stop();
goog.fx.css3.Transition.base(this, 'disposeInternal');
};
/**
* Pausing CSS3 Transitions in not supported.
* @override
*/
goog.fx.css3.Transition.prototype.pause = function() {
goog.asserts.assert(false, 'Css3 transitions does not support pause action.');
};
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: chrishenry@google.com (Chris Henry)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.css3.Transition
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.fx.css3.TransitionTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,219 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.css3.TransitionTest');
goog.setTestOnly('goog.fx.css3.TransitionTest');
goog.require('goog.dispose');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.fx.Transition');
goog.require('goog.fx.css3.Transition');
goog.require('goog.style.transition');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
var transition;
var element;
var mockClock;
function createTransition(element, duration) {
return new goog.fx.css3.Transition(
element, duration, {'opacity': 0}, {'opacity': 1},
{property: 'opacity', duration: duration, timing: 'ease-in', delay: 0});
}
function setUp() {
mockClock = new goog.testing.MockClock(true);
element = goog.dom.createElement('div');
document.body.appendChild(element);
}
function tearDown() {
goog.dispose(transition);
goog.dispose(mockClock);
goog.dom.removeNode(element);
}
function testPlayEventFiredOnPlay() {
if (!goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var handlerCalled = false;
goog.events.listen(transition, goog.fx.Transition.EventType.PLAY,
function() {
handlerCalled = true;
});
transition.play();
assertTrue(handlerCalled);
}
function testBeginEventFiredOnPlay() {
if (!goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var handlerCalled = false;
goog.events.listen(transition, goog.fx.Transition.EventType.BEGIN,
function() {
handlerCalled = true;
});
transition.play();
assertTrue(handlerCalled);
}
function testFinishEventsFiredAfterFinish() {
if (!goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var finishHandlerCalled = false;
var endHandlerCalled = false;
goog.events.listen(transition, goog.fx.Transition.EventType.FINISH,
function() {
finishHandlerCalled = true;
});
goog.events.listen(transition, goog.fx.Transition.EventType.END,
function() {
endHandlerCalled = true;
});
transition.play();
mockClock.tick(10000);
assertTrue(finishHandlerCalled);
assertTrue(endHandlerCalled);
}
function testEventsWhenTransitionIsUnsupported() {
if (goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var stopHandlerCalled = false;
var finishHandlerCalled = false, endHandlerCalled = false;
var beginHandlerCalled = false, playHandlerCalled = false;
goog.events.listen(transition, goog.fx.Transition.EventType.BEGIN,
function() {
beginHandlerCalled = true;
});
goog.events.listen(transition, goog.fx.Transition.EventType.PLAY,
function() {
playHandlerCalled = true;
});
goog.events.listen(transition, goog.fx.Transition.EventType.FINISH,
function() {
finishHandlerCalled = true;
});
goog.events.listen(transition, goog.fx.Transition.EventType.END,
function() {
endHandlerCalled = true;
});
goog.events.listen(transition, goog.fx.Transition.EventType.STOP,
function() {
stopHandlerCalled = true;
});
assertFalse(transition.play());
assertTrue(beginHandlerCalled);
assertTrue(playHandlerCalled);
assertTrue(endHandlerCalled);
assertTrue(finishHandlerCalled);
transition.stop();
assertFalse(stopHandlerCalled);
}
function testCallingStopDuringAnimationWorks() {
if (!goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var stopHandler = goog.testing.recordFunction();
var endHandler = goog.testing.recordFunction();
var finishHandler = goog.testing.recordFunction();
goog.events.listen(transition, goog.fx.Transition.EventType.STOP,
stopHandler);
goog.events.listen(transition, goog.fx.Transition.EventType.END,
endHandler);
goog.events.listen(transition, goog.fx.Transition.EventType.FINISH,
finishHandler);
transition.play();
mockClock.tick(1);
transition.stop();
assertEquals(1, stopHandler.getCallCount());
assertEquals(1, endHandler.getCallCount());
mockClock.tick(10000);
assertEquals(0, finishHandler.getCallCount());
}
function testCallingStopImmediatelyWorks() {
if (!goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var stopHandler = goog.testing.recordFunction();
var endHandler = goog.testing.recordFunction();
var finishHandler = goog.testing.recordFunction();
goog.events.listen(transition, goog.fx.Transition.EventType.STOP,
stopHandler);
goog.events.listen(transition, goog.fx.Transition.EventType.END,
endHandler);
goog.events.listen(transition, goog.fx.Transition.EventType.FINISH,
finishHandler);
transition.play();
transition.stop();
assertEquals(1, stopHandler.getCallCount());
assertEquals(1, endHandler.getCallCount());
mockClock.tick(10000);
assertEquals(0, finishHandler.getCallCount());
}
function testCallingStopAfterAnimationDoesNothing() {
if (!goog.style.transition.isSupported()) return;
transition = createTransition(element, 10);
var stopHandler = goog.testing.recordFunction();
var endHandler = goog.testing.recordFunction();
var finishHandler = goog.testing.recordFunction();
goog.events.listen(transition, goog.fx.Transition.EventType.STOP,
stopHandler);
goog.events.listen(transition, goog.fx.Transition.EventType.END,
endHandler);
goog.events.listen(transition, goog.fx.Transition.EventType.FINISH,
finishHandler);
transition.play();
mockClock.tick(10000);
transition.stop();
assertEquals(0, stopHandler.getCallCount());
assertEquals(1, endHandler.getCallCount());
assertEquals(1, finishHandler.getCallCount());
}
@@ -0,0 +1,130 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An animation class that animates CSS sprites by changing the
* CSS background-position.
*
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/cssspriteanimation.html
*/
goog.provide('goog.fx.CssSpriteAnimation');
goog.require('goog.fx.Animation');
/**
* This animation class is used to animate a CSS sprite (moving a background
* image). This moves through a series of images in a single image sprite. By
* default, the animation loops when done. Looping can be disabled by setting
* {@code opt_disableLoop} and results in the animation stopping on the last
* image in the image sprite. You should set up the {@code background-image}
* and size in a CSS rule for the relevant element.
*
* @param {Element} element The HTML element to animate the background for.
* @param {goog.math.Size} size The size of one image in the image sprite.
* @param {goog.math.Box} box The box describing the layout of the sprites to
* use in the large image. The sprites can be position horizontally or
* vertically and using a box here allows the implementation to know which
* way to go.
* @param {number} time The duration in milliseconds for one iteration of the
* animation. For example, if the sprite contains 4 images and the duration
* is set to 400ms then each sprite will be displayed for 100ms.
* @param {function(number) : number=} opt_acc Acceleration function,
* returns 0-1 for inputs 0-1. This can be used to make certain frames be
* shown for a longer period of time.
* @param {boolean=} opt_disableLoop Whether the animation should be halted
* after a single loop of the images in the sprite.
*
* @constructor
* @extends {goog.fx.Animation}
* @final
*/
goog.fx.CssSpriteAnimation = function(element, size, box, time, opt_acc,
opt_disableLoop) {
var start = [box.left, box.top];
// We never draw for the end so we do not need to subtract for the size
var end = [box.right, box.bottom];
goog.fx.CssSpriteAnimation.base(
this, 'constructor', start, end, time, opt_acc);
/**
* HTML element that will be used in the animation.
* @type {Element}
* @private
*/
this.element_ = element;
/**
* The size of an individual sprite in the image sprite.
* @type {goog.math.Size}
* @private
*/
this.size_ = size;
/**
* Whether the animation should be halted after a single loop of the images
* in the sprite.
* @type {boolean}
* @private
*/
this.disableLoop_ = !!opt_disableLoop;
};
goog.inherits(goog.fx.CssSpriteAnimation, goog.fx.Animation);
/** @override */
goog.fx.CssSpriteAnimation.prototype.onAnimate = function() {
// Round to nearest sprite.
var x = -Math.floor(this.coords[0] / this.size_.width) * this.size_.width;
var y = -Math.floor(this.coords[1] / this.size_.height) * this.size_.height;
this.element_.style.backgroundPosition = x + 'px ' + y + 'px';
goog.fx.CssSpriteAnimation.base(this, 'onAnimate');
};
/** @override */
goog.fx.CssSpriteAnimation.prototype.onFinish = function() {
if (!this.disableLoop_) {
this.play(true);
}
goog.fx.CssSpriteAnimation.base(this, 'onFinish');
};
/**
* Clears the background position style set directly on the element
* by the animation. Allows to apply CSS styling for background position on the
* same element when the sprite animation is not runniing.
*/
goog.fx.CssSpriteAnimation.prototype.clearSpritePosition = function() {
var style = this.element_.style;
style.backgroundPosition = '';
if (typeof style.backgroundPositionX != 'undefined') {
// IE needs to clear x and y to actually clear the position
style.backgroundPositionX = '';
style.backgroundPositionY = '';
}
};
/** @override */
goog.fx.CssSpriteAnimation.prototype.disposeInternal = function() {
goog.fx.CssSpriteAnimation.superClass_.disposeInternal.call(this);
this.element_ = null;
};
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: arv@google.com (Erik Arvidsson)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.CssSpriteAnimation
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.CssSpriteAnimationTest');
</script>
<style>
#test {
width: 10px;
height: 10px;
}
</style>
</head>
<body>
<div id="test">
</div>
</body>
</html>
@@ -0,0 +1,153 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.CssSpriteAnimationTest');
goog.setTestOnly('goog.fx.CssSpriteAnimationTest');
goog.require('goog.fx.CssSpriteAnimation');
goog.require('goog.math.Box');
goog.require('goog.math.Size');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
var el;
var size;
var box;
var time = 1000;
var anim, clock;
function setUpPage() {
clock = new goog.testing.MockClock(true);
el = document.getElementById('test');
size = new goog.math.Size(10, 10);
box = new goog.math.Box(0, 10, 100, 0);
}
function tearDownPage() {
clock.dispose();
}
function tearDown() {
anim.clearSpritePosition();
anim.dispose();
}
function assertBackgroundPosition(x, y) {
if (typeof el.style.backgroundPositionX != 'undefined') {
assertEquals(x + 'px', el.style.backgroundPositionX);
assertEquals(y + 'px', el.style.backgroundPositionY);
} else {
var bgPos = el.style.backgroundPosition;
var message = 'Expected <' + x + 'px ' + y + 'px>, found <' + bgPos + '>';
if (x == y) {
// when x and y are the same the browser sometimes collapse the prop
assertTrue(message,
bgPos == x || // in case of 0 without a unit
bgPos == x + 'px' ||
bgPos == x + ' ' + y ||
bgPos == x + 'px ' + y + 'px');
} else {
assertTrue(message,
bgPos == x + ' ' + y ||
bgPos == x + 'px ' + y ||
bgPos == x + ' ' + y + 'px' ||
bgPos == x + 'px ' + y + 'px');
}
}
}
function testAnimation() {
anim = new goog.fx.CssSpriteAnimation(el, size, box, time);
anim.play();
assertBackgroundPosition(0, 0);
clock.tick(5);
assertBackgroundPosition(0, 0);
clock.tick(95);
assertBackgroundPosition(0, -10);
clock.tick(100);
assertBackgroundPosition(0, -20);
clock.tick(300);
assertBackgroundPosition(0, -50);
clock.tick(400);
assertBackgroundPosition(0, -90);
// loop around to starting position
clock.tick(100);
assertBackgroundPosition(0, 0);
assertTrue(anim.isPlaying());
assertFalse(anim.isStopped());
clock.tick(100);
assertBackgroundPosition(0, -10);
}
function testAnimation_disableLoop() {
anim = new goog.fx.CssSpriteAnimation(el, size, box, time, undefined,
true /* opt_disableLoop */);
anim.play();
assertBackgroundPosition(0, 0);
clock.tick(5);
assertBackgroundPosition(0, 0);
clock.tick(95);
assertBackgroundPosition(0, -10);
clock.tick(100);
assertBackgroundPosition(0, -20);
clock.tick(300);
assertBackgroundPosition(0, -50);
clock.tick(400);
assertBackgroundPosition(0, -90);
// loop around to starting position
clock.tick(100);
assertBackgroundPosition(0, -90);
assertTrue(anim.isStopped());
assertFalse(anim.isPlaying());
clock.tick(100);
assertBackgroundPosition(0, -90);
}
function testClearSpritePosition() {
anim = new goog.fx.CssSpriteAnimation(el, size, box, time);
anim.play();
assertBackgroundPosition(0, 0);
clock.tick(100);
assertBackgroundPosition(0, -10);
anim.clearSpritePosition();
if (typeof el.style.backgroundPositionX != 'undefined') {
assertEquals('', el.style.backgroundPositionX);
assertEquals('', el.style.backgroundPositionY);
}
assertEquals('', el.style.backgroundPosition);
}
@@ -0,0 +1,686 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Predefined DHTML animations such as slide, resize and fade.
*
* @see ../demos/effects.html
*/
goog.provide('goog.fx.dom');
goog.provide('goog.fx.dom.BgColorTransform');
goog.provide('goog.fx.dom.ColorTransform');
goog.provide('goog.fx.dom.Fade');
goog.provide('goog.fx.dom.FadeIn');
goog.provide('goog.fx.dom.FadeInAndShow');
goog.provide('goog.fx.dom.FadeOut');
goog.provide('goog.fx.dom.FadeOutAndHide');
goog.provide('goog.fx.dom.PredefinedEffect');
goog.provide('goog.fx.dom.Resize');
goog.provide('goog.fx.dom.ResizeHeight');
goog.provide('goog.fx.dom.ResizeWidth');
goog.provide('goog.fx.dom.Scroll');
goog.provide('goog.fx.dom.Slide');
goog.provide('goog.fx.dom.SlideFrom');
goog.provide('goog.fx.dom.Swipe');
goog.require('goog.color');
goog.require('goog.events');
goog.require('goog.fx.Animation');
goog.require('goog.fx.Transition');
goog.require('goog.style');
goog.require('goog.style.bidi');
/**
* Abstract class that provides reusable functionality for predefined animations
* that manipulate a single DOM element
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start Array for start coordinates.
* @param {Array<number>} end Array for end coordinates.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.Animation}
* @constructor
*/
goog.fx.dom.PredefinedEffect = function(element, start, end, time, opt_acc) {
goog.fx.Animation.call(this, start, end, time, opt_acc);
/**
* DOM Node that will be used in the animation
* @type {Element}
*/
this.element = element;
/**
* Whether the element is rendered right-to-left. We cache this here for
* efficiency.
* @private {boolean|undefined}
*/
this.rightToLeft_;
};
goog.inherits(goog.fx.dom.PredefinedEffect, goog.fx.Animation);
/**
* Called to update the style of the element.
* @protected
*/
goog.fx.dom.PredefinedEffect.prototype.updateStyle = goog.nullFunction;
/**
* Whether the DOM element being manipulated is rendered right-to-left.
* @return {boolean} True if the DOM element is rendered right-to-left, false
* otherwise.
*/
goog.fx.dom.PredefinedEffect.prototype.isRightToLeft = function() {
if (!goog.isDef(this.rightToLeft_)) {
this.rightToLeft_ = goog.style.isRightToLeft(this.element);
}
return this.rightToLeft_;
};
/** @override */
goog.fx.dom.PredefinedEffect.prototype.onAnimate = function() {
this.updateStyle();
goog.fx.dom.PredefinedEffect.superClass_.onAnimate.call(this);
};
/** @override */
goog.fx.dom.PredefinedEffect.prototype.onEnd = function() {
this.updateStyle();
goog.fx.dom.PredefinedEffect.superClass_.onEnd.call(this);
};
/** @override */
goog.fx.dom.PredefinedEffect.prototype.onBegin = function() {
this.updateStyle();
goog.fx.dom.PredefinedEffect.superClass_.onBegin.call(this);
};
/**
* Creates an animation object that will slide an element from A to B. (This
* in effect automatically sets up the onanimate event for an Animation object)
*
* Start and End should be 2 dimensional arrays
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 2D array for start coordinates (X, Y).
* @param {Array<number>} end 2D array for end coordinates (X, Y).
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.Slide = function(element, start, end, time, opt_acc) {
if (start.length != 2 || end.length != 2) {
throw Error('Start and end points must be 2D');
}
goog.fx.dom.PredefinedEffect.apply(this, arguments);
};
goog.inherits(goog.fx.dom.Slide, goog.fx.dom.PredefinedEffect);
/** @override */
goog.fx.dom.Slide.prototype.updateStyle = function() {
var pos = (this.isRightPositioningForRtlEnabled() && this.isRightToLeft()) ?
'right' : 'left';
this.element.style[pos] = Math.round(this.coords[0]) + 'px';
this.element.style.top = Math.round(this.coords[1]) + 'px';
};
/**
* Slides an element from its current position.
*
* @param {Element} element DOM node to be used in the animation.
* @param {Array<number>} end 2D array for end coordinates (X, Y).
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.Slide}
* @constructor
*/
goog.fx.dom.SlideFrom = function(element, end, time, opt_acc) {
var offsetLeft = this.isRightPositioningForRtlEnabled() ?
goog.style.bidi.getOffsetStart(element) : element.offsetLeft;
var start = [offsetLeft, element.offsetTop];
goog.fx.dom.Slide.call(this, element, start, end, time, opt_acc);
};
goog.inherits(goog.fx.dom.SlideFrom, goog.fx.dom.Slide);
/** @override */
goog.fx.dom.SlideFrom.prototype.onBegin = function() {
var offsetLeft = this.isRightPositioningForRtlEnabled() ?
goog.style.bidi.getOffsetStart(this.element) : this.element.offsetLeft;
this.startPoint = [offsetLeft, this.element.offsetTop];
goog.fx.dom.SlideFrom.superClass_.onBegin.call(this);
};
/**
* Creates an animation object that will slide an element into its final size.
* Requires that the element is absolutely positioned.
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 2D array for start size (W, H).
* @param {Array<number>} end 2D array for end size (W, H).
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.Swipe = function(element, start, end, time, opt_acc) {
if (start.length != 2 || end.length != 2) {
throw Error('Start and end points must be 2D');
}
goog.fx.dom.PredefinedEffect.apply(this, arguments);
/**
* Maximum width for element.
* @type {number}
* @private
*/
this.maxWidth_ = Math.max(this.endPoint[0], this.startPoint[0]);
/**
* Maximum height for element.
* @type {number}
* @private
*/
this.maxHeight_ = Math.max(this.endPoint[1], this.startPoint[1]);
};
goog.inherits(goog.fx.dom.Swipe, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will resize an element by setting its width,
* height and clipping.
* @protected
* @override
*/
goog.fx.dom.Swipe.prototype.updateStyle = function() {
var x = this.coords[0];
var y = this.coords[1];
this.clip_(Math.round(x), Math.round(y), this.maxWidth_, this.maxHeight_);
this.element.style.width = Math.round(x) + 'px';
var marginX = (this.isRightPositioningForRtlEnabled() &&
this.isRightToLeft()) ? 'marginRight' : 'marginLeft';
this.element.style[marginX] = Math.round(x) - this.maxWidth_ + 'px';
this.element.style.marginTop = Math.round(y) - this.maxHeight_ + 'px';
};
/**
* Helper function for setting element clipping.
* @param {number} x Current element width.
* @param {number} y Current element height.
* @param {number} w Maximum element width.
* @param {number} h Maximum element height.
* @private
*/
goog.fx.dom.Swipe.prototype.clip_ = function(x, y, w, h) {
this.element.style.clip =
'rect(' + (h - y) + 'px ' + w + 'px ' + h + 'px ' + (w - x) + 'px)';
};
/**
* Creates an animation object that will scroll an element from A to B.
*
* Start and End should be 2 dimensional arrays
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 2D array for start scroll left and top.
* @param {Array<number>} end 2D array for end scroll left and top.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.Scroll = function(element, start, end, time, opt_acc) {
if (start.length != 2 || end.length != 2) {
throw Error('Start and end points must be 2D');
}
goog.fx.dom.PredefinedEffect.apply(this, arguments);
};
goog.inherits(goog.fx.dom.Scroll, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will set the scroll position of an element.
* @protected
* @override
*/
goog.fx.dom.Scroll.prototype.updateStyle = function() {
if (this.isRightPositioningForRtlEnabled()) {
goog.style.bidi.setScrollOffset(this.element, Math.round(this.coords[0]));
} else {
this.element.scrollLeft = Math.round(this.coords[0]);
}
this.element.scrollTop = Math.round(this.coords[1]);
};
/**
* Creates an animation object that will resize an element between two widths
* and heights.
*
* Start and End should be 2 dimensional arrays
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 2D array for start width and height.
* @param {Array<number>} end 2D array for end width and height.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.Resize = function(element, start, end, time, opt_acc) {
if (start.length != 2 || end.length != 2) {
throw Error('Start and end points must be 2D');
}
goog.fx.dom.PredefinedEffect.apply(this, arguments);
};
goog.inherits(goog.fx.dom.Resize, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will resize an element by setting its width and
* height.
* @protected
* @override
*/
goog.fx.dom.Resize.prototype.updateStyle = function() {
this.element.style.width = Math.round(this.coords[0]) + 'px';
this.element.style.height = Math.round(this.coords[1]) + 'px';
};
/**
* Creates an animation object that will resize an element between two widths
*
* Start and End should be numbers
*
* @param {Element} element Dom Node to be used in the animation.
* @param {number} start Start width.
* @param {number} end End width.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.ResizeWidth = function(element, start, end, time, opt_acc) {
goog.fx.dom.PredefinedEffect.call(this, element, [start],
[end], time, opt_acc);
};
goog.inherits(goog.fx.dom.ResizeWidth, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will resize an element by setting its width.
* @protected
* @override
*/
goog.fx.dom.ResizeWidth.prototype.updateStyle = function() {
this.element.style.width = Math.round(this.coords[0]) + 'px';
};
/**
* Creates an animation object that will resize an element between two heights
*
* Start and End should be numbers
*
* @param {Element} element Dom Node to be used in the animation.
* @param {number} start Start height.
* @param {number} end End height.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.ResizeHeight = function(element, start, end, time, opt_acc) {
goog.fx.dom.PredefinedEffect.call(this, element, [start],
[end], time, opt_acc);
};
goog.inherits(goog.fx.dom.ResizeHeight, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will resize an element by setting its height.
* @protected
* @override
*/
goog.fx.dom.ResizeHeight.prototype.updateStyle = function() {
this.element.style.height = Math.round(this.coords[0]) + 'px';
};
/**
* Creates an animation object that fades the opacity of an element between two
* limits.
*
* Start and End should be floats between 0 and 1
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>|number} start 1D Array or Number with start opacity.
* @param {Array<number>|number} end 1D Array or Number for end opacity.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.Fade = function(element, start, end, time, opt_acc) {
if (goog.isNumber(start)) start = [start];
if (goog.isNumber(end)) end = [end];
goog.fx.dom.Fade.base(this, 'constructor',
element, start, end, time, opt_acc);
if (start.length != 1 || end.length != 1) {
throw Error('Start and end points must be 1D');
}
/**
* The last opacity we set, or -1 for not set.
* @private {number}
*/
this.lastOpacityUpdate_ = goog.fx.dom.Fade.OPACITY_UNSET_;
};
goog.inherits(goog.fx.dom.Fade, goog.fx.dom.PredefinedEffect);
/**
* The quantization of opacity values to use.
* @private {number}
*/
goog.fx.dom.Fade.TOLERANCE_ = 1.0 / 0x400; // 10-bit color
/**
* Value indicating that the opacity must be set on next update.
* @private {number}
*/
goog.fx.dom.Fade.OPACITY_UNSET_ = -1;
/**
* Animation event handler that will set the opacity of an element.
* @protected
* @override
*/
goog.fx.dom.Fade.prototype.updateStyle = function() {
var opacity = this.coords[0];
var delta = Math.abs(opacity - this.lastOpacityUpdate_);
// In order to keep eager browsers from over-rendering, only update
// on a potentially visible change in opacity.
if (delta >= goog.fx.dom.Fade.TOLERANCE_) {
goog.style.setOpacity(this.element, opacity);
this.lastOpacityUpdate_ = opacity;
}
};
/** @override */
goog.fx.dom.Fade.prototype.onBegin = function() {
this.lastOpacityUpdate_ = goog.fx.dom.Fade.OPACITY_UNSET_;
goog.fx.dom.Fade.base(this, 'onBegin');
};
/** @override */
goog.fx.dom.Fade.prototype.onEnd = function() {
this.lastOpacityUpdate_ = goog.fx.dom.Fade.OPACITY_UNSET_;
goog.fx.dom.Fade.base(this, 'onEnd');
};
/**
* Animation event handler that will show the element.
*/
goog.fx.dom.Fade.prototype.show = function() {
this.element.style.display = '';
};
/**
* Animation event handler that will hide the element
*/
goog.fx.dom.Fade.prototype.hide = function() {
this.element.style.display = 'none';
};
/**
* Fades an element out from full opacity to completely transparent.
*
* @param {Element} element Dom Node to be used in the animation.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.Fade}
* @constructor
*/
goog.fx.dom.FadeOut = function(element, time, opt_acc) {
goog.fx.dom.Fade.call(this, element, 1, 0, time, opt_acc);
};
goog.inherits(goog.fx.dom.FadeOut, goog.fx.dom.Fade);
/**
* Fades an element in from completely transparent to fully opacity.
*
* @param {Element} element Dom Node to be used in the animation.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.Fade}
* @constructor
*/
goog.fx.dom.FadeIn = function(element, time, opt_acc) {
goog.fx.dom.Fade.call(this, element, 0, 1, time, opt_acc);
};
goog.inherits(goog.fx.dom.FadeIn, goog.fx.dom.Fade);
/**
* Fades an element out from full opacity to completely transparent and then
* sets the display to 'none'
*
* @param {Element} element Dom Node to be used in the animation.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.Fade}
* @constructor
*/
goog.fx.dom.FadeOutAndHide = function(element, time, opt_acc) {
goog.fx.dom.Fade.call(this, element, 1, 0, time, opt_acc);
};
goog.inherits(goog.fx.dom.FadeOutAndHide, goog.fx.dom.Fade);
/** @override */
goog.fx.dom.FadeOutAndHide.prototype.onBegin = function() {
this.show();
goog.fx.dom.FadeOutAndHide.superClass_.onBegin.call(this);
};
/** @override */
goog.fx.dom.FadeOutAndHide.prototype.onEnd = function() {
this.hide();
goog.fx.dom.FadeOutAndHide.superClass_.onEnd.call(this);
};
/**
* Sets an element's display to be visible and then fades an element in from
* completely transparent to fully opaque.
*
* @param {Element} element Dom Node to be used in the animation.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.Fade}
* @constructor
*/
goog.fx.dom.FadeInAndShow = function(element, time, opt_acc) {
goog.fx.dom.Fade.call(this, element, 0, 1, time, opt_acc);
};
goog.inherits(goog.fx.dom.FadeInAndShow, goog.fx.dom.Fade);
/** @override */
goog.fx.dom.FadeInAndShow.prototype.onBegin = function() {
this.show();
goog.fx.dom.FadeInAndShow.superClass_.onBegin.call(this);
};
/**
* Provides a transformation of an elements background-color.
*
* Start and End should be 3D arrays representing R,G,B
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 3D Array for RGB of start color.
* @param {Array<number>} end 3D Array for RGB of end color.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @extends {goog.fx.dom.PredefinedEffect}
* @constructor
*/
goog.fx.dom.BgColorTransform = function(element, start, end, time, opt_acc) {
if (start.length != 3 || end.length != 3) {
throw Error('Start and end points must be 3D');
}
goog.fx.dom.PredefinedEffect.apply(this, arguments);
};
goog.inherits(goog.fx.dom.BgColorTransform, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will set the background-color of an element
*/
goog.fx.dom.BgColorTransform.prototype.setColor = function() {
var coordsAsInts = [];
for (var i = 0; i < this.coords.length; i++) {
coordsAsInts[i] = Math.round(this.coords[i]);
}
var color = 'rgb(' + coordsAsInts.join(',') + ')';
this.element.style.backgroundColor = color;
};
/** @override */
goog.fx.dom.BgColorTransform.prototype.updateStyle = function() {
this.setColor();
};
/**
* Fade elements background color from start color to the element's current
* background color.
*
* Start should be a 3D array representing R,G,B
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 3D Array for RGB of start color.
* @param {number} time Length of animation in milliseconds.
* @param {goog.events.EventHandler=} opt_eventHandler Optional event handler
* to use when listening for events.
*/
goog.fx.dom.bgColorFadeIn = function(element, start, time, opt_eventHandler) {
var initialBgColor = element.style.backgroundColor || '';
var computedBgColor = goog.style.getBackgroundColor(element);
var end;
if (computedBgColor && computedBgColor != 'transparent' &&
computedBgColor != 'rgba(0, 0, 0, 0)') {
end = goog.color.hexToRgb(goog.color.parse(computedBgColor).hex);
} else {
end = [255, 255, 255];
}
var anim = new goog.fx.dom.BgColorTransform(element, start, end, time);
function setBgColor() {
element.style.backgroundColor = initialBgColor;
}
if (opt_eventHandler) {
opt_eventHandler.listen(
anim, goog.fx.Transition.EventType.END, setBgColor);
} else {
goog.events.listen(
anim, goog.fx.Transition.EventType.END, setBgColor);
}
anim.play();
};
/**
* Provides a transformation of an elements color.
*
* @param {Element} element Dom Node to be used in the animation.
* @param {Array<number>} start 3D Array representing R,G,B.
* @param {Array<number>} end 3D Array representing R,G,B.
* @param {number} time Length of animation in milliseconds.
* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
* @constructor
* @extends {goog.fx.dom.PredefinedEffect}
*/
goog.fx.dom.ColorTransform = function(element, start, end, time, opt_acc) {
if (start.length != 3 || end.length != 3) {
throw Error('Start and end points must be 3D');
}
goog.fx.dom.PredefinedEffect.apply(this, arguments);
};
goog.inherits(goog.fx.dom.ColorTransform, goog.fx.dom.PredefinedEffect);
/**
* Animation event handler that will set the color of an element.
* @protected
* @override
*/
goog.fx.dom.ColorTransform.prototype.updateStyle = function() {
var coordsAsInts = [];
for (var i = 0; i < this.coords.length; i++) {
coordsAsInts[i] = Math.round(this.coords[i]);
}
var color = 'rgb(' + coordsAsInts.join(',') + ')';
this.element.style.color = color;
};
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Single Element Drag and Drop.
*
* Drag and drop implementation for sources/targets consisting of a single
* element.
*
* @author eae@google.com (Emil A Eklund)
* @see ../demos/dragdrop.html
*/
goog.provide('goog.fx.DragDrop');
goog.require('goog.fx.AbstractDragDrop');
goog.require('goog.fx.DragDropItem');
/**
* Drag/drop implementation for creating drag sources/drop targets consisting of
* a single HTML Element.
*
* @param {Element|string} element Dom Node, or string representation of node
* id, to be used as drag source/drop target.
* @param {Object=} opt_data Data associated with the source/target.
* @throws Error If no element argument is provided or if the type is invalid
* @extends {goog.fx.AbstractDragDrop}
* @constructor
*/
goog.fx.DragDrop = function(element, opt_data) {
goog.fx.AbstractDragDrop.call(this);
var item = new goog.fx.DragDropItem(element, opt_data);
item.setParent(this);
this.items_.push(item);
};
goog.inherits(goog.fx.DragDrop, goog.fx.AbstractDragDrop);
@@ -0,0 +1,109 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Multiple Element Drag and Drop.
*
* Drag and drop implementation for sources/targets consisting of multiple
* elements.
*
* @author eae@google.com (Emil A Eklund)
* @see ../demos/dragdrop.html
*/
goog.provide('goog.fx.DragDropGroup');
goog.require('goog.dom');
goog.require('goog.fx.AbstractDragDrop');
goog.require('goog.fx.DragDropItem');
/**
* Drag/drop implementation for creating drag sources/drop targets consisting of
* multiple HTML Elements (items). All items share the same drop target(s) but
* can be dragged individually.
*
* @extends {goog.fx.AbstractDragDrop}
* @constructor
*/
goog.fx.DragDropGroup = function() {
goog.fx.AbstractDragDrop.call(this);
};
goog.inherits(goog.fx.DragDropGroup, goog.fx.AbstractDragDrop);
/**
* Add item to drag object.
*
* @param {Element|string} element Dom Node, or string representation of node
* id, to be used as drag source/drop target.
* @param {Object=} opt_data Data associated with the source/target.
* @throws Error If no element argument is provided or if the type is
* invalid
* @override
*/
goog.fx.DragDropGroup.prototype.addItem = function(element, opt_data) {
var item = new goog.fx.DragDropItem(element, opt_data);
this.addDragDropItem(item);
};
/**
* Add DragDropItem to drag object.
*
* @param {goog.fx.DragDropItem} item DragDropItem being added to the
* drag object.
* @throws Error If no element argument is provided or if the type is
* invalid
*/
goog.fx.DragDropGroup.prototype.addDragDropItem = function(item) {
item.setParent(this);
this.items_.push(item);
if (this.isInitialized()) {
this.initItem(item);
}
};
/**
* Remove item from drag object.
*
* @param {Element|string} element Dom Node, or string representation of node
* id, that was previously added with addItem().
*/
goog.fx.DragDropGroup.prototype.removeItem = function(element) {
element = goog.dom.getElement(element);
for (var item, i = 0; item = this.items_[i]; i++) {
if (item.element == element) {
this.items_.splice(i, 1);
this.disposeItem(item);
break;
}
}
};
/**
* Marks the supplied list of items as selected. A drag operation for any of the
* selected items will affect all of them.
*
* @param {Array<goog.fx.DragDropItem>} list List of items to select or null to
* clear selection.
*
* TODO(eae): Not yet implemented.
*/
goog.fx.DragDropGroup.prototype.setSelection = function(list) {
};
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2010 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.DragDropGroup
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.DragDropGroupTest');
</script>
</head>
<body>
<div id="s1" class="s">
</div>
<div id="s2" class="s">
</div>
<div id="t1" class="t">
</div>
<div id="t2" class="t">
</div>
</body>
</html>
@@ -0,0 +1,229 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.DragDropGroupTest');
goog.setTestOnly('goog.fx.DragDropGroupTest');
goog.require('goog.events');
goog.require('goog.fx.DragDropGroup');
goog.require('goog.testing.jsunit');
var s1;
var s2;
var t1;
var t2;
var source = null;
var target = null;
function setUpPage() {
s1 = document.getElementById('s1');
s2 = document.getElementById('s2');
t1 = document.getElementById('t1');
t2 = document.getElementById('t2');
}
function setUp() {
source = new goog.fx.DragDropGroup();
source.setSourceClass('ss');
source.setTargetClass('st');
target = new goog.fx.DragDropGroup();
target.setSourceClass('ts');
target.setTargetClass('tt');
source.addTarget(target);
}
function tearDown() {
source.removeItems();
target.removeItems();
}
function addElementsToGroups() {
source.addItem(s1);
source.addItem(s2);
target.addItem(t1);
target.addItem(t2);
}
function testAddItemsBeforeInit() {
addElementsToGroups();
source.init();
target.init();
assertEquals(2, source.items_.length);
assertEquals(2, target.items_.length);
assertEquals('s ss', s1.className);
assertEquals('s ss', s2.className);
assertEquals('t tt', t1.className);
assertEquals('t tt', t2.className);
assertTrue(goog.events.hasListener(s1));
assertTrue(goog.events.hasListener(s2));
assertFalse(goog.events.hasListener(t1));
assertFalse(goog.events.hasListener(t2));
}
function testAddItemsAfterInit() {
source.init();
target.init();
addElementsToGroups();
assertEquals(2, source.items_.length);
assertEquals(2, target.items_.length);
assertEquals('s ss', s1.className);
assertEquals('s ss', s2.className);
assertEquals('t tt', t1.className);
assertEquals('t tt', t2.className);
assertTrue(goog.events.hasListener(s1));
assertTrue(goog.events.hasListener(s2));
assertFalse(goog.events.hasListener(t1));
assertFalse(goog.events.hasListener(t2));
}
function testRemoveItems() {
source.init();
target.init();
addElementsToGroups();
assertEquals(2, source.items_.length);
assertEquals(s1, source.items_[0].element);
assertEquals(s2, source.items_[1].element);
assertEquals('s ss', s1.className);
assertEquals('s ss', s2.className);
assertTrue(goog.events.hasListener(s1));
assertTrue(goog.events.hasListener(s2));
source.removeItems();
assertEquals(0, source.items_.length);
assertEquals('s', s1.className);
assertEquals('s', s2.className);
assertFalse(goog.events.hasListener(s1));
assertFalse(goog.events.hasListener(s2));
}
function testRemoveSourceItem1() {
source.init();
target.init();
addElementsToGroups();
assertEquals(2, source.items_.length);
assertEquals(s1, source.items_[0].element);
assertEquals(s2, source.items_[1].element);
assertEquals('s ss', s1.className);
assertEquals('s ss', s2.className);
assertTrue(goog.events.hasListener(s1));
assertTrue(goog.events.hasListener(s2));
source.removeItem(s1);
assertEquals(1, source.items_.length);
assertEquals(s2, source.items_[0].element);
assertEquals('s', s1.className);
assertEquals('s ss', s2.className);
assertFalse(goog.events.hasListener(s1));
assertTrue(goog.events.hasListener(s2));
}
function testRemoveSourceItem2() {
source.init();
target.init();
addElementsToGroups();
assertEquals(2, source.items_.length);
assertEquals(s1, source.items_[0].element);
assertEquals(s2, source.items_[1].element);
assertEquals('s ss', s1.className);
assertEquals('s ss', s2.className);
assertTrue(goog.events.hasListener(s1));
assertTrue(goog.events.hasListener(s2));
source.removeItem(s2);
assertEquals(1, source.items_.length);
assertEquals(s1, source.items_[0].element);
assertEquals('s ss', s1.className);
assertEquals('s', s2.className);
assertTrue(goog.events.hasListener(s1));
assertFalse(goog.events.hasListener(s2));
}
function testRemoveTargetItem1() {
source.init();
target.init();
addElementsToGroups();
assertEquals(2, target.items_.length);
assertEquals(t1, target.items_[0].element);
assertEquals(t2, target.items_[1].element);
assertEquals('t tt', t1.className);
assertEquals('t tt', t2.className);
assertFalse(goog.events.hasListener(t1));
assertFalse(goog.events.hasListener(t2));
target.removeItem(t1);
assertEquals(1, target.items_.length);
assertEquals(t2, target.items_[0].element);
assertEquals('t', t1.className);
assertEquals('t tt', t2.className);
assertFalse(goog.events.hasListener(t1));
assertFalse(goog.events.hasListener(t2));
}
function testRemoveTargetItem2() {
source.init();
target.init();
addElementsToGroups();
assertEquals(2, target.items_.length);
assertEquals(t1, target.items_[0].element);
assertEquals(t2, target.items_[1].element);
assertEquals('t tt', t1.className);
assertEquals('t tt', t2.className);
assertFalse(goog.events.hasListener(t1));
assertFalse(goog.events.hasListener(t2));
target.removeItem(t2);
assertEquals(1, target.items_.length);
assertEquals(t1, target.items_[0].element);
assertEquals('t tt', t1.className);
assertEquals('t', t2.className);
assertFalse(goog.events.hasListener(t1));
assertFalse(goog.events.hasListener(t2));
}
@@ -0,0 +1,847 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Drag Utilities.
*
* Provides extensible functionality for drag & drop behaviour.
*
* @see ../demos/drag.html
* @see ../demos/dragger.html
*/
goog.provide('goog.fx.DragEvent');
goog.provide('goog.fx.Dragger');
goog.provide('goog.fx.Dragger.EventType');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Rect');
goog.require('goog.style');
goog.require('goog.style.bidi');
goog.require('goog.userAgent');
/**
* A class that allows mouse or touch-based dragging (moving) of an element
*
* @param {Element} target The element that will be dragged.
* @param {Element=} opt_handle An optional handle to control the drag, if null
* the target is used.
* @param {goog.math.Rect=} opt_limits Object containing left, top, width,
* and height.
*
* @extends {goog.events.EventTarget}
* @constructor
*/
goog.fx.Dragger = function(target, opt_handle, opt_limits) {
goog.events.EventTarget.call(this);
this.target = target;
this.handle = opt_handle || target;
this.limits = opt_limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
this.document_ = goog.dom.getOwnerDocument(target);
this.eventHandler_ = new goog.events.EventHandler(this);
this.registerDisposable(this.eventHandler_);
// Add listener. Do not use the event handler here since the event handler is
// used for listeners added and removed during the drag operation.
goog.events.listen(this.handle,
[goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN],
this.startDrag, false, this);
};
goog.inherits(goog.fx.Dragger, goog.events.EventTarget);
// Dragger is meant to be extended, but defines most properties on its
// prototype, thus making it unsuitable for sealing.
goog.tagUnsealableClass(goog.fx.Dragger);
/**
* Whether setCapture is supported by the browser.
* @type {boolean}
* @private
*/
goog.fx.Dragger.HAS_SET_CAPTURE_ =
// IE and Gecko after 1.9.3 has setCapture
// WebKit does not yet: https://bugs.webkit.org/show_bug.cgi?id=27330
goog.userAgent.IE ||
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.3');
/**
* Creates copy of node being dragged. This is a utility function to be used
* wherever it is inappropriate for the original source to follow the mouse
* cursor itself.
*
* @param {Element} sourceEl Element to copy.
* @return {!Element} The clone of {@code sourceEl}.
*/
goog.fx.Dragger.cloneNode = function(sourceEl) {
var clonedEl = /** @type {Element} */ (sourceEl.cloneNode(true)),
origTexts = sourceEl.getElementsByTagName('textarea'),
dragTexts = clonedEl.getElementsByTagName('textarea');
// Cloning does not copy the current value of textarea elements, so correct
// this manually.
for (var i = 0; i < origTexts.length; i++) {
dragTexts[i].value = origTexts[i].value;
}
switch (sourceEl.tagName.toLowerCase()) {
case 'tr':
return goog.dom.createDom(
'table', null, goog.dom.createDom('tbody', null, clonedEl));
case 'td':
case 'th':
return goog.dom.createDom(
'table', null, goog.dom.createDom('tbody', null, goog.dom.createDom(
'tr', null, clonedEl)));
case 'textarea':
clonedEl.value = sourceEl.value;
default:
return clonedEl;
}
};
/**
* Constants for event names.
* @enum {string}
*/
goog.fx.Dragger.EventType = {
// The drag action was canceled before the START event. Possible reasons:
// disabled dragger, dragging with the right mouse button or releasing the
// button before reaching the hysteresis distance.
EARLY_CANCEL: 'earlycancel',
START: 'start',
BEFOREDRAG: 'beforedrag',
DRAG: 'drag',
END: 'end'
};
/**
* Reference to drag target element.
* @type {Element}
*/
goog.fx.Dragger.prototype.target;
/**
* Reference to the handler that initiates the drag.
* @type {Element}
*/
goog.fx.Dragger.prototype.handle;
/**
* Object representing the limits of the drag region.
* @type {goog.math.Rect}
*/
goog.fx.Dragger.prototype.limits;
/**
* Whether the element is rendered right-to-left. We initialize this lazily.
* @type {boolean|undefined}}
* @private
*/
goog.fx.Dragger.prototype.rightToLeft_;
/**
* Current x position of mouse or touch relative to viewport.
* @type {number}
*/
goog.fx.Dragger.prototype.clientX = 0;
/**
* Current y position of mouse or touch relative to viewport.
* @type {number}
*/
goog.fx.Dragger.prototype.clientY = 0;
/**
* Current x position of mouse or touch relative to screen. Deprecated because
* it doesn't take into affect zoom level or pixel density.
* @type {number}
* @deprecated Consider switching to clientX instead.
*/
goog.fx.Dragger.prototype.screenX = 0;
/**
* Current y position of mouse or touch relative to screen. Deprecated because
* it doesn't take into affect zoom level or pixel density.
* @type {number}
* @deprecated Consider switching to clientY instead.
*/
goog.fx.Dragger.prototype.screenY = 0;
/**
* The x position where the first mousedown or touchstart occurred.
* @type {number}
*/
goog.fx.Dragger.prototype.startX = 0;
/**
* The y position where the first mousedown or touchstart occurred.
* @type {number}
*/
goog.fx.Dragger.prototype.startY = 0;
/**
* Current x position of drag relative to target's parent.
* @type {number}
*/
goog.fx.Dragger.prototype.deltaX = 0;
/**
* Current y position of drag relative to target's parent.
* @type {number}
*/
goog.fx.Dragger.prototype.deltaY = 0;
/**
* The current page scroll value.
* @type {goog.math.Coordinate}
*/
goog.fx.Dragger.prototype.pageScroll;
/**
* Whether dragging is currently enabled.
* @type {boolean}
* @private
*/
goog.fx.Dragger.prototype.enabled_ = true;
/**
* Whether object is currently being dragged.
* @type {boolean}
* @private
*/
goog.fx.Dragger.prototype.dragging_ = false;
/**
* The amount of distance, in pixels, after which a mousedown or touchstart is
* considered a drag.
* @type {number}
* @private
*/
goog.fx.Dragger.prototype.hysteresisDistanceSquared_ = 0;
/**
* Timestamp of when the mousedown or touchstart occurred.
* @type {number}
* @private
*/
goog.fx.Dragger.prototype.mouseDownTime_ = 0;
/**
* Reference to a document object to use for the events.
* @type {Document}
* @private
*/
goog.fx.Dragger.prototype.document_;
/**
* The SCROLL event target used to make drag element follow scrolling.
* @type {EventTarget}
* @private
*/
goog.fx.Dragger.prototype.scrollTarget_;
/**
* Whether IE drag events cancelling is on.
* @type {boolean}
* @private
*/
goog.fx.Dragger.prototype.ieDragStartCancellingOn_ = false;
/**
* Whether the dragger implements the changes described in http://b/6324964,
* making it truly RTL. This is a temporary flag to allow clients to transition
* to the new behavior at their convenience. At some point it will be the
* default.
* @type {boolean}
* @private
*/
goog.fx.Dragger.prototype.useRightPositioningForRtl_ = false;
/**
* Turns on/off true RTL behavior. This should be called immediately after
* construction. This is a temporary flag to allow clients to transition
* to the new component at their convenience. At some point true will be the
* default.
* @param {boolean} useRightPositioningForRtl True if "right" should be used for
* positioning, false if "left" should be used for positioning.
*/
goog.fx.Dragger.prototype.enableRightPositioningForRtl =
function(useRightPositioningForRtl) {
this.useRightPositioningForRtl_ = useRightPositioningForRtl;
};
/**
* Returns the event handler, intended for subclass use.
* @return {!goog.events.EventHandler<T>} The event handler.
* @this T
* @template T
*/
goog.fx.Dragger.prototype.getHandler = function() {
// TODO(user): templated "this" values currently result in "this" being
// "unknown" in the body of the function.
var self = /** @type {goog.fx.Dragger} */ (this);
return self.eventHandler_;
};
/**
* Sets (or reset) the Drag limits after a Dragger is created.
* @param {goog.math.Rect?} limits Object containing left, top, width,
* height for new Dragger limits. If target is right-to-left and
* enableRightPositioningForRtl(true) is called, then rect is interpreted as
* right, top, width, and height.
*/
goog.fx.Dragger.prototype.setLimits = function(limits) {
this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN);
};
/**
* Sets the distance the user has to drag the element before a drag operation is
* started.
* @param {number} distance The number of pixels after which a mousedown and
* move is considered a drag.
*/
goog.fx.Dragger.prototype.setHysteresis = function(distance) {
this.hysteresisDistanceSquared_ = Math.pow(distance, 2);
};
/**
* Gets the distance the user has to drag the element before a drag operation is
* started.
* @return {number} distance The number of pixels after which a mousedown and
* move is considered a drag.
*/
goog.fx.Dragger.prototype.getHysteresis = function() {
return Math.sqrt(this.hysteresisDistanceSquared_);
};
/**
* Sets the SCROLL event target to make drag element follow scrolling.
*
* @param {EventTarget} scrollTarget The event target that dispatches SCROLL
* events.
*/
goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) {
this.scrollTarget_ = scrollTarget;
};
/**
* Enables cancelling of built-in IE drag events.
* @param {boolean} cancelIeDragStart Whether to enable cancelling of IE
* dragstart event.
*/
goog.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) {
this.ieDragStartCancellingOn_ = cancelIeDragStart;
};
/**
* @return {boolean} Whether the dragger is enabled.
*/
goog.fx.Dragger.prototype.getEnabled = function() {
return this.enabled_;
};
/**
* Set whether dragger is enabled
* @param {boolean} enabled Whether dragger is enabled.
*/
goog.fx.Dragger.prototype.setEnabled = function(enabled) {
this.enabled_ = enabled;
};
/** @override */
goog.fx.Dragger.prototype.disposeInternal = function() {
goog.fx.Dragger.superClass_.disposeInternal.call(this);
goog.events.unlisten(this.handle,
[goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN],
this.startDrag, false, this);
this.cleanUpAfterDragging_();
this.target = null;
this.handle = null;
};
/**
* Whether the DOM element being manipulated is rendered right-to-left.
* @return {boolean} True if the DOM element is rendered right-to-left, false
* otherwise.
* @private
*/
goog.fx.Dragger.prototype.isRightToLeft_ = function() {
if (!goog.isDef(this.rightToLeft_)) {
this.rightToLeft_ = goog.style.isRightToLeft(this.target);
}
return this.rightToLeft_;
};
/**
* Event handler that is used to start the drag
* @param {goog.events.BrowserEvent} e Event object.
*/
goog.fx.Dragger.prototype.startDrag = function(e) {
var isMouseDown = e.type == goog.events.EventType.MOUSEDOWN;
// Dragger.startDrag() can be called by AbstractDragDrop with a mousemove
// event and IE does not report pressed mouse buttons on mousemove. Also,
// it does not make sense to check for the button if the user is already
// dragging.
if (this.enabled_ && !this.dragging_ &&
(!isMouseDown || e.isMouseActionButton())) {
this.maybeReinitTouchEvent_(e);
if (this.hysteresisDistanceSquared_ == 0) {
if (this.fireDragStart_(e)) {
this.dragging_ = true;
e.preventDefault();
} else {
// If the start drag is cancelled, don't setup for a drag.
return;
}
} else {
// Need to preventDefault for hysteresis to prevent page getting selected.
e.preventDefault();
}
this.setupDragHandlers();
this.clientX = this.startX = e.clientX;
this.clientY = this.startY = e.clientY;
this.screenX = e.screenX;
this.screenY = e.screenY;
this.computeInitialPosition();
this.pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll();
this.mouseDownTime_ = goog.now();
} else {
this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
}
};
/**
* Sets up event handlers when dragging starts.
* @protected
*/
goog.fx.Dragger.prototype.setupDragHandlers = function() {
var doc = this.document_;
var docEl = doc.documentElement;
// Use bubbling when we have setCapture since we got reports that IE has
// problems with the capturing events in combination with setCapture.
var useCapture = !goog.fx.Dragger.HAS_SET_CAPTURE_;
this.eventHandler_.listen(doc,
[goog.events.EventType.TOUCHMOVE, goog.events.EventType.MOUSEMOVE],
this.handleMove_, useCapture);
this.eventHandler_.listen(doc,
[goog.events.EventType.TOUCHEND, goog.events.EventType.MOUSEUP],
this.endDrag, useCapture);
if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
docEl.setCapture(false);
this.eventHandler_.listen(docEl,
goog.events.EventType.LOSECAPTURE,
this.endDrag);
} else {
// Make sure we stop the dragging if the window loses focus.
// Don't use capture in this listener because we only want to end the drag
// if the actual window loses focus. Since blur events do not bubble we use
// a bubbling listener on the window.
this.eventHandler_.listen(goog.dom.getWindow(doc),
goog.events.EventType.BLUR,
this.endDrag);
}
if (goog.userAgent.IE && this.ieDragStartCancellingOn_) {
// Cancel IE's 'ondragstart' event.
this.eventHandler_.listen(doc, goog.events.EventType.DRAGSTART,
goog.events.Event.preventDefault);
}
if (this.scrollTarget_) {
this.eventHandler_.listen(this.scrollTarget_, goog.events.EventType.SCROLL,
this.onScroll_, useCapture);
}
};
/**
* Fires a goog.fx.Dragger.EventType.START event.
* @param {goog.events.BrowserEvent} e Browser event that triggered the drag.
* @return {boolean} False iff preventDefault was called on the DragEvent.
* @private
*/
goog.fx.Dragger.prototype.fireDragStart_ = function(e) {
return this.dispatchEvent(new goog.fx.DragEvent(
goog.fx.Dragger.EventType.START, this, e.clientX, e.clientY, e));
};
/**
* Unregisters the event handlers that are only active during dragging, and
* releases mouse capture.
* @private
*/
goog.fx.Dragger.prototype.cleanUpAfterDragging_ = function() {
this.eventHandler_.removeAll();
if (goog.fx.Dragger.HAS_SET_CAPTURE_) {
this.document_.releaseCapture();
}
};
/**
* Event handler that is used to end the drag.
* @param {goog.events.BrowserEvent} e Event object.
* @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
*/
goog.fx.Dragger.prototype.endDrag = function(e, opt_dragCanceled) {
this.cleanUpAfterDragging_();
if (this.dragging_) {
this.maybeReinitTouchEvent_(e);
this.dragging_ = false;
var x = this.limitX(this.deltaX);
var y = this.limitY(this.deltaY);
var dragCanceled = opt_dragCanceled ||
e.type == goog.events.EventType.TOUCHCANCEL;
this.dispatchEvent(new goog.fx.DragEvent(
goog.fx.Dragger.EventType.END, this, e.clientX, e.clientY, e, x, y,
dragCanceled));
} else {
this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL);
}
};
/**
* Event handler that is used to end the drag by cancelling it.
* @param {goog.events.BrowserEvent} e Event object.
*/
goog.fx.Dragger.prototype.endDragCancel = function(e) {
this.endDrag(e, true);
};
/**
* Re-initializes the event with the first target touch event or, in the case
* of a stop event, the last changed touch.
* @param {goog.events.BrowserEvent} e A TOUCH... event.
* @private
*/
goog.fx.Dragger.prototype.maybeReinitTouchEvent_ = function(e) {
var type = e.type;
if (type == goog.events.EventType.TOUCHSTART ||
type == goog.events.EventType.TOUCHMOVE) {
e.init(e.getBrowserEvent().targetTouches[0], e.currentTarget);
} else if (type == goog.events.EventType.TOUCHEND ||
type == goog.events.EventType.TOUCHCANCEL) {
e.init(e.getBrowserEvent().changedTouches[0], e.currentTarget);
}
};
/**
* Event handler that is used on mouse / touch move to update the drag
* @param {goog.events.BrowserEvent} e Event object.
* @private
*/
goog.fx.Dragger.prototype.handleMove_ = function(e) {
if (this.enabled_) {
this.maybeReinitTouchEvent_(e);
// dx in right-to-left cases is relative to the right.
var sign = this.useRightPositioningForRtl_ &&
this.isRightToLeft_() ? -1 : 1;
var dx = sign * (e.clientX - this.clientX);
var dy = e.clientY - this.clientY;
this.clientX = e.clientX;
this.clientY = e.clientY;
this.screenX = e.screenX;
this.screenY = e.screenY;
if (!this.dragging_) {
var diffX = this.startX - this.clientX;
var diffY = this.startY - this.clientY;
var distance = diffX * diffX + diffY * diffY;
if (distance > this.hysteresisDistanceSquared_) {
if (this.fireDragStart_(e)) {
this.dragging_ = true;
} else {
// DragListGroup disposes of the dragger if BEFOREDRAGSTART is
// canceled.
if (!this.isDisposed()) {
this.endDrag(e);
}
return;
}
}
}
var pos = this.calculatePosition_(dx, dy);
var x = pos.x;
var y = pos.y;
if (this.dragging_) {
var rv = this.dispatchEvent(new goog.fx.DragEvent(
goog.fx.Dragger.EventType.BEFOREDRAG, this, e.clientX, e.clientY,
e, x, y));
// Only do the defaultAction and dispatch drag event if predrag didn't
// prevent default
if (rv) {
this.doDrag(e, x, y, false);
e.preventDefault();
}
}
}
};
/**
* Calculates the drag position.
*
* @param {number} dx The horizontal movement delta.
* @param {number} dy The vertical movement delta.
* @return {!goog.math.Coordinate} The newly calculated drag element position.
* @private
*/
goog.fx.Dragger.prototype.calculatePosition_ = function(dx, dy) {
// Update the position for any change in body scrolling
var pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll();
dx += pageScroll.x - this.pageScroll.x;
dy += pageScroll.y - this.pageScroll.y;
this.pageScroll = pageScroll;
this.deltaX += dx;
this.deltaY += dy;
var x = this.limitX(this.deltaX);
var y = this.limitY(this.deltaY);
return new goog.math.Coordinate(x, y);
};
/**
* Event handler for scroll target scrolling.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.fx.Dragger.prototype.onScroll_ = function(e) {
var pos = this.calculatePosition_(0, 0);
e.clientX = this.clientX;
e.clientY = this.clientY;
this.doDrag(e, pos.x, pos.y, true);
};
/**
* @param {goog.events.BrowserEvent} e The closure object
* representing the browser event that caused a drag event.
* @param {number} x The new horizontal position for the drag element.
* @param {number} y The new vertical position for the drag element.
* @param {boolean} dragFromScroll Whether dragging was caused by scrolling
* the associated scroll target.
* @protected
*/
goog.fx.Dragger.prototype.doDrag = function(e, x, y, dragFromScroll) {
this.defaultAction(x, y);
this.dispatchEvent(new goog.fx.DragEvent(
goog.fx.Dragger.EventType.DRAG, this, e.clientX, e.clientY, e, x, y));
};
/**
* Returns the 'real' x after limits are applied (allows for some
* limits to be undefined).
* @param {number} x X-coordinate to limit.
* @return {number} The 'real' X-coordinate after limits are applied.
*/
goog.fx.Dragger.prototype.limitX = function(x) {
var rect = this.limits;
var left = !isNaN(rect.left) ? rect.left : null;
var width = !isNaN(rect.width) ? rect.width : 0;
var maxX = left != null ? left + width : Infinity;
var minX = left != null ? left : -Infinity;
return Math.min(maxX, Math.max(minX, x));
};
/**
* Returns the 'real' y after limits are applied (allows for some
* limits to be undefined).
* @param {number} y Y-coordinate to limit.
* @return {number} The 'real' Y-coordinate after limits are applied.
*/
goog.fx.Dragger.prototype.limitY = function(y) {
var rect = this.limits;
var top = !isNaN(rect.top) ? rect.top : null;
var height = !isNaN(rect.height) ? rect.height : 0;
var maxY = top != null ? top + height : Infinity;
var minY = top != null ? top : -Infinity;
return Math.min(maxY, Math.max(minY, y));
};
/**
* Overridable function for computing the initial position of the target
* before dragging begins.
* @protected
*/
goog.fx.Dragger.prototype.computeInitialPosition = function() {
this.deltaX = this.useRightPositioningForRtl_ ?
goog.style.bidi.getOffsetStart(this.target) : this.target.offsetLeft;
this.deltaY = this.target.offsetTop;
};
/**
* Overridable function for handling the default action of the drag behaviour.
* Normally this is simply moving the element to x,y though in some cases it
* might be used to resize the layer. This is basically a shortcut to
* implementing a default ondrag event handler.
* @param {number} x X-coordinate for target element. In right-to-left, x this
* is the number of pixels the target should be moved to from the right.
* @param {number} y Y-coordinate for target element.
*/
goog.fx.Dragger.prototype.defaultAction = function(x, y) {
if (this.useRightPositioningForRtl_ && this.isRightToLeft_()) {
this.target.style.right = x + 'px';
} else {
this.target.style.left = x + 'px';
}
this.target.style.top = y + 'px';
};
/**
* @return {boolean} Whether the dragger is currently in the midst of a drag.
*/
goog.fx.Dragger.prototype.isDragging = function() {
return this.dragging_;
};
/**
* Object representing a drag event
* @param {string} type Event type.
* @param {goog.fx.Dragger} dragobj Drag object initiating event.
* @param {number} clientX X-coordinate relative to the viewport.
* @param {number} clientY Y-coordinate relative to the viewport.
* @param {goog.events.BrowserEvent} browserEvent The closure object
* representing the browser event that caused this drag event.
* @param {number=} opt_actX Optional actual x for drag if it has been limited.
* @param {number=} opt_actY Optional actual y for drag if it has been limited.
* @param {boolean=} opt_dragCanceled Whether the drag has been canceled.
* @constructor
* @extends {goog.events.Event}
*/
goog.fx.DragEvent = function(type, dragobj, clientX, clientY, browserEvent,
opt_actX, opt_actY, opt_dragCanceled) {
goog.events.Event.call(this, type);
/**
* X-coordinate relative to the viewport
* @type {number}
*/
this.clientX = clientX;
/**
* Y-coordinate relative to the viewport
* @type {number}
*/
this.clientY = clientY;
/**
* The closure object representing the browser event that caused this drag
* event.
* @type {goog.events.BrowserEvent}
*/
this.browserEvent = browserEvent;
/**
* The real x-position of the drag if it has been limited
* @type {number}
*/
this.left = goog.isDef(opt_actX) ? opt_actX : dragobj.deltaX;
/**
* The real y-position of the drag if it has been limited
* @type {number}
*/
this.top = goog.isDef(opt_actY) ? opt_actY : dragobj.deltaY;
/**
* Reference to the drag object for this event
* @type {goog.fx.Dragger}
*/
this.dragger = dragobj;
/**
* Whether drag was canceled with this event. Used to differentiate between
* a legitimate drag END that can result in an action and a drag END which is
* a result of a drag cancelation. For now it can happen 1) with drag END
* event on FireFox when user drags the mouse out of the window, 2) with
* drag END event on IE7 which is generated on MOUSEMOVE event when user
* moves the mouse into the document after the mouse button has been
* released, 3) when TOUCHCANCEL is raised instead of TOUCHEND (on touch
* events).
* @type {boolean}
*/
this.dragCanceled = !!opt_dragCanceled;
};
goog.inherits(goog.fx.DragEvent, goog.events.Event);
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.Dragger
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.DraggerTest');
</script>
</head>
<body>
<div id="sandbox">
</div>
<div id="sandbox_rtl" dir="rtl" style="overflow: auto; position:absolute; top:20px; left: 20px;
width: 100px; height: 100px; background: red;">
</div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</body>
</html>
@@ -0,0 +1,459 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.DraggerTest');
goog.setTestOnly('goog.fx.DraggerTest');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.fx.Dragger');
goog.require('goog.math.Rect');
goog.require('goog.style.bidi');
goog.require('goog.testing.StrictMock');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
var HAS_SET_CAPTURE = goog.fx.Dragger.HAS_SET_CAPTURE_;
var target;
var targetRtl;
function setUp() {
var sandbox = goog.dom.getElement('sandbox');
target = goog.dom.createDom('div', {
'id': 'target',
'style': 'display:none;position:absolute;top:15px;left:10px'
});
sandbox.appendChild(target);
sandbox.appendChild(goog.dom.createDom('div', {'id': 'handle'}));
var sandboxRtl = goog.dom.getElement('sandbox_rtl');
targetRtl = goog.dom.createDom('div', {
'id': 'target_rtl',
'style': 'position:absolute; top:15px; right:10px; width:10px; ' +
'height: 10px; background: green;'
});
sandboxRtl.appendChild(targetRtl);
sandboxRtl.appendChild(goog.dom.createDom('div', {
'id': 'background_rtl',
'style': 'width: 10000px;height:50px;position:absolute;color:blue;'
}));
sandboxRtl.appendChild(goog.dom.createDom('div', {'id': 'handle_rtl'}));
}
function tearDown() {
goog.dom.getElement('sandbox').innerHTML = '';
goog.dom.getElement('sandbox_rtl').innerHTML = '';
goog.events.removeAll(document);
}
function testStartDrag() {
runStartDragTest('handle', target);
}
function testStartDrag_rtl() {
runStartDragTest('handle_rtl', targetRtl);
}
function runStartDragTest(handleId, targetElement) {
var dragger =
new goog.fx.Dragger(targetElement, goog.dom.getElement(handleId));
if (handleId == 'handle_rtl') {
dragger.enableRightPositioningForRtl(true);
}
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEDOWN;
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(true);
e.preventDefault();
e.isMouseActionButton().$returns(true);
e.preventDefault();
e.$replay();
goog.events.listen(dragger, goog.fx.Dragger.EventType.START, function() {
targetElement.style.display = 'block';
});
dragger.startDrag(e);
assertTrue('Start drag with no hysteresis must actually start the drag.',
dragger.isDragging());
if (handleId == 'handle_rtl') {
assertEquals(10, goog.style.bidi.getOffsetStart(targetElement));
}
assertEquals('Dragger startX must match event\'s clientX.',
1, dragger.startX);
assertEquals('Dragger clientX must match event\'s clientX',
1, dragger.clientX);
assertEquals('Dragger startY must match event\'s clientY.',
2, dragger.startY);
assertEquals('Dragger clientY must match event\'s clientY',
2, dragger.clientY);
assertEquals('Dragger deltaX must match target\'s offsetLeft',
10, dragger.deltaX);
assertEquals('Dragger deltaY must match target\'s offsetTop',
15, dragger.deltaY);
dragger = new goog.fx.Dragger(targetElement, goog.dom.getElement(handleId));
dragger.setHysteresis(1);
dragger.startDrag(e);
assertFalse('Start drag with a valid non-zero hysteresis should not start ' +
'the drag.', dragger.isDragging());
e.$verify();
}
/**
* @bug 1381317 Cancelling start drag didn't end the attempt to drag.
*/
function testStartDrag_Cancel() {
var dragger = new goog.fx.Dragger(target);
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEDOWN;
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(true);
e.$replay();
goog.events.listen(dragger, goog.fx.Dragger.EventType.START, function(e) {
// Cancel drag.
e.preventDefault();
});
dragger.startDrag(e);
assertFalse('Start drag must have been cancelled.',
dragger.isDragging());
assertFalse('Dragger must not have registered mousemove handlers.',
goog.events.hasListener(dragger.document_,
goog.events.EventType.MOUSEMOVE, !HAS_SET_CAPTURE));
assertFalse('Dragger must not have registered mouseup handlers.',
goog.events.hasListener(dragger.document_, goog.events.EventType.MOUSEUP,
!HAS_SET_CAPTURE));
e.$verify();
}
/**
* Tests that start drag happens on left mousedown.
*/
function testStartDrag_LeftMouseDownOnly() {
var dragger = new goog.fx.Dragger(target);
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEDOWN;
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(false);
e.$replay();
goog.events.listen(dragger, goog.fx.Dragger.EventType.START, function(e) {
fail('No drag START event should have been dispatched');
});
dragger.startDrag(e);
assertFalse('Start drag must have been cancelled.',
dragger.isDragging());
assertFalse('Dragger must not have registered mousemove handlers.',
goog.events.hasListener(dragger.document_,
goog.events.EventType.MOUSEMOVE, true));
assertFalse('Dragger must not have registered mouseup handlers.',
goog.events.hasListener(dragger.document_, goog.events.EventType.MOUSEUP,
true));
e.$verify();
}
/**
* Tests that start drag happens on other event type than MOUSEDOWN.
*/
function testStartDrag_MouseMove() {
var dragger = new goog.fx.Dragger(target);
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEMOVE;
e.clientX = 1;
e.clientY = 2;
e.preventDefault();
e.$replay();
var startDragFired = false;
goog.events.listen(dragger, goog.fx.Dragger.EventType.START, function(e) {
startDragFired = true;
});
dragger.startDrag(e);
assertTrue('Dragging should be in progress.', dragger.isDragging());
assertTrue('Start drag event should have fired.', startDragFired);
assertTrue('Dragger must have registered mousemove handlers.',
goog.events.hasListener(dragger.document_,
goog.events.EventType.MOUSEMOVE, !HAS_SET_CAPTURE));
assertTrue('Dragger must have registered mouseup handlers.',
goog.events.hasListener(dragger.document_, goog.events.EventType.MOUSEUP,
!HAS_SET_CAPTURE));
e.$verify();
}
/**
* @bug 1381317 Cancelling start drag didn't end the attempt to drag.
*/
function testHandleMove_Cancel() {
var dragger = new goog.fx.Dragger(target);
dragger.setHysteresis(5);
goog.events.listen(dragger, goog.fx.Dragger.EventType.START, function(e) {
// Cancel drag.
e.preventDefault();
});
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(true).
$anyTimes();
e.preventDefault();
e.$replay();
dragger.startDrag(e);
assertFalse('Start drag must not start drag because of hysterisis.',
dragger.isDragging());
assertTrue('Dragger must have registered mousemove handlers.',
goog.events.hasListener(dragger.document_,
goog.events.EventType.MOUSEMOVE, !HAS_SET_CAPTURE));
assertTrue('Dragger must have registered mouseup handlers.',
goog.events.hasListener(dragger.document_, goog.events.EventType.MOUSEUP,
!HAS_SET_CAPTURE));
e.clientX = 10;
e.clientY = 10;
dragger.handleMove_(e);
assertFalse('Drag must be cancelled.', dragger.isDragging());
assertFalse('Dragger must unregistered mousemove handlers.',
goog.events.hasListener(dragger.document_,
goog.events.EventType.MOUSEMOVE, true));
assertFalse('Dragger must unregistered mouseup handlers.',
goog.events.hasListener(dragger.document_, goog.events.EventType.MOUSEUP,
true));
e.$verify();
}
/**
* @bug 1714667 IE<9 built in drag and drop handling stops dragging.
*/
function testIeDragStartCancelling() {
// Testing only IE<9.
if (!goog.userAgent.IE || goog.userAgent.isVersionOrHigher(9)) {
return;
}
// Built in 'dragstart' cancelling not enabled.
var dragger = new goog.fx.Dragger(target);
var e = new goog.events.Event(goog.events.EventType.MOUSEDOWN);
e.clientX = 1;
e.clientY = 2;
e.button = 1; // IE only constant for left button.
var be = new goog.events.BrowserEvent(e);
dragger.startDrag(be);
assertTrue('The drag should have started.', dragger.isDragging());
e = new goog.events.Event(goog.events.EventType.DRAGSTART);
e.target = dragger.document_.documentElement;
assertTrue('The event should not be canceled.',
goog.testing.events.fireBrowserEvent(e));
dragger.dispose();
// Built in 'dragstart' cancelling enabled.
dragger = new goog.fx.Dragger(target);
dragger.setCancelIeDragStart(true);
e = new goog.events.Event(goog.events.EventType.MOUSEDOWN);
e.clientX = 1;
e.clientY = 2;
e.button = 1; // IE only constant for left button.
be = new goog.events.BrowserEvent(e);
dragger.startDrag(be);
assertTrue('The drag should have started.', dragger.isDragging());
e = new goog.events.Event(goog.events.EventType.DRAGSTART);
e.target = dragger.document_.documentElement;
assertFalse('The event should be canceled.',
goog.testing.events.fireBrowserEvent(e));
dragger.dispose();
}
/** @bug 1680770 */
function testOnWindowMouseOut() {
// Test older Gecko browsers - FireFox 2.
if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9a')) {
var dragger = new goog.fx.Dragger(target);
var dragCanceled = false;
goog.events.listen(dragger, goog.fx.Dragger.EventType.END, function(e) {
dragCanceled = e.dragCanceled;
});
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEDOWN;
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(true);
e.preventDefault();
e.$replay();
dragger.startDrag(e);
e.$verify();
assertTrue(dragger.isDragging());
e = new goog.events.BrowserEvent();
e.type = goog.events.EventType.MOUSEOUT;
e.target = goog.dom.getElement('sandbox');
e.currentTarget = window.top;
e.relatedTarget = target;
dragger.onWindowMouseOut_(e);
assertFalse('Drag is not canceled for normal in-window mouseout.',
dragCanceled);
assertTrue('Must not stop dragging for normal in-window mouseout.',
dragger.isDragging());
dragCanceled = false;
delete e.relatedTarget;
e.target = goog.dom.createDom('iframe');
dragger.onWindowMouseOut_(e);
assertFalse('Drag is not canceled for mousing into iframe.',
dragCanceled);
assertTrue('Must not stop dragging for mousing into iframe.',
dragger.isDragging());
dragCanceled = false;
e.target = target;
dragger.onWindowMouseOut_(e);
assertTrue('Drag is canceled for real mouse out of top window.',
dragCanceled);
assertFalse('Must stop dragging for real mouse out of top window.',
dragger.isDragging());
}
}
function testLimits() {
var dragger = new goog.fx.Dragger(target);
assertEquals(100, dragger.limitX(100));
assertEquals(100, dragger.limitY(100));
dragger.setLimits(new goog.math.Rect(10, 20, 30, 40));
assertEquals(10, dragger.limitX(0));
assertEquals(40, dragger.limitX(100));
assertEquals(20, dragger.limitY(0));
assertEquals(60, dragger.limitY(100));
}
function testWindowBlur() {
if (!goog.fx.Dragger.HAS_SET_CAPTURE_) {
var dragger = new goog.fx.Dragger(target);
var dragEnded = false;
goog.events.listen(dragger, goog.fx.Dragger.EventType.END, function(e) {
dragEnded = true;
});
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEDOWN;
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(true);
e.preventDefault();
e.$replay();
dragger.startDrag(e);
e.$verify();
assertTrue(dragger.isDragging());
e = new goog.events.BrowserEvent();
e.type = goog.events.EventType.BLUR;
e.target = window;
e.currentTarget = window;
goog.testing.events.fireBrowserEvent(e);
assertTrue(dragEnded);
}
}
function testBlur() {
if (!goog.fx.Dragger.HAS_SET_CAPTURE_) {
var dragger = new goog.fx.Dragger(target);
var dragEnded = false;
goog.events.listen(dragger, goog.fx.Dragger.EventType.END, function(e) {
dragEnded = true;
});
var e = new goog.testing.StrictMock(goog.events.BrowserEvent);
e.type = goog.events.EventType.MOUSEDOWN;
e.clientX = 1;
e.clientY = 2;
e.isMouseActionButton().$returns(true);
e.preventDefault();
e.$replay();
dragger.startDrag(e);
e.$verify();
assertTrue(dragger.isDragging());
e = new goog.events.BrowserEvent();
e.type = goog.events.EventType.BLUR;
e.target = document.body;
e.currentTarget = document.body;
// Blur events do not bubble but the test event system does not emulate that
// part so we add a capturing listener on the target and stops the
// propagation at the target, preventing any event from bubbling.
goog.events.listen(document.body, goog.events.EventType.BLUR, function(e) {
e.propagationStopped_ = true;
}, true);
goog.testing.events.fireBrowserEvent(e);
assertFalse(dragEnded);
}
}
function testCloneNode() {
var element = goog.dom.createDom('div');
element.innerHTML =
'<input type="hidden" value="v0">' +
'<textarea>v1</textarea>' +
'<textarea>v2</textarea>';
element.childNodes[0].value = '\'new\'\n"value"';
element.childNodes[1].value = '<' + '/textarea>&lt;3';
element.childNodes[2].value = '<script>\n\talert("oops!");<' + '/script>';
var clone = goog.fx.Dragger.cloneNode(element);
assertEquals(element.childNodes[0].value, clone.childNodes[0].value);
assertEquals(element.childNodes[1].value, clone.childNodes[1].value);
assertEquals(element.childNodes[2].value, clone.childNodes[2].value);
clone = goog.fx.Dragger.cloneNode(element.childNodes[2]);
assertEquals(element.childNodes[2].value, clone.value);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<title>
Closure Unit Tests - goog.fx.DragListGroup
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.DragListGroupTest');
</script>
<style>
.opacity-40 {
opacity: 0.4;
-moz-opacity: 0.4;
filter: alpha(opacity=40);
}
.cursor_move {
cursor: move;
-moz-user-select: none;
-webkit-user-select: none;
}
.cursor_pointer {
cursor: pointer;
}
.blue_bg {
background-color: #0000CC;
}
</style>
</head>
<body>
<div id="sandbox">
</div>
</body>
</html>
@@ -0,0 +1,397 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.DragListGroupTest');
goog.setTestOnly('goog.fx.DragListGroupTest');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.fx.DragEvent');
goog.require('goog.fx.DragListDirection');
goog.require('goog.fx.DragListGroup');
goog.require('goog.fx.Dragger');
goog.require('goog.math.Coordinate');
goog.require('goog.object');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
/** @type {goog.fx.DragListGroup} */
var dlg;
/** @type {goog.dom.Element} */
var list;
/** @type {goog.events.BrowserEvent} */
var event;
/**
* The number of event listeners registered by the DragListGroup after the
* init() call.
* @type {number}
*/
var initialListenerCount;
/**
* Type of events fired by the DragListGroup.
* @type {!Array<string>}
*/
var firedEventTypes;
function setUp() {
var sandbox = goog.dom.getElement('sandbox');
list = goog.dom.createDom('div', {'id': 'horiz_div'});
list.appendChild(
goog.dom.createDom('div', null, goog.dom.createTextNode('1')));
list.appendChild(
goog.dom.createDom('div', null, goog.dom.createTextNode('2')));
list.appendChild(
goog.dom.createDom('div', null, goog.dom.createTextNode('3')));
sandbox.appendChild(list);
dlg = new goog.fx.DragListGroup();
dlg.setDragItemHoverClass('opacity_40', 'cursor_move');
dlg.setDragItemHandleHoverClass('opacity_40', 'cursor_pointer');
dlg.setCurrDragItemClass('blue_bg', 'opacity_40');
dlg.setDraggerElClass('cursor_move', 'blue_bg');
dlg.addDragList(list, goog.fx.DragListDirection.RIGHT);
dlg.init();
initialListenerCount = goog.object.getCount(dlg.eventHandler_.keys_);
event = new goog.events.BrowserEvent();
event.currentTarget = list.getElementsByTagName('div')[0];
firedEventTypes = [];
goog.events.listen(dlg,
goog.object.getValues(goog.fx.DragListGroup.EventType),
function(e) {
firedEventTypes.push(e.type);
});
}
function tearDown() {
dlg.dispose();
goog.dom.getElement('sandbox').innerHTML = '';
}
/**
* Test the initial assumptions.
*
* Verify that the setter methods work properly, i.e., the CSS classes are
* stored in the private arrays after init() but are not added yet to target.
* (Since initially, we are not yet hovering over any list, in particular,
* over this target.)
*/
function testSettersAfterInit() {
assertTrue(goog.array.equals(dlg.dragItemHoverClasses_,
['opacity_40', 'cursor_move']));
assertTrue(goog.array.equals(dlg.dragItemHandleHoverClasses_,
['opacity_40', 'cursor_pointer']));
assertTrue(goog.array.equals(dlg.currDragItemClasses_,
['blue_bg', 'opacity_40']));
assertFalse('Should have no cursor_move class after init',
goog.dom.classlist.contains(event.currentTarget, 'cursor_move'));
assertFalse('Should have no cursor_pointer class after init',
goog.dom.classlist.contains(event.currentTarget, 'cursor_pointer'));
assertFalse('Should have no opacity_40 class after init',
goog.dom.classlist.contains(event.currentTarget, 'opacity_40'));
assertFalse('Should not have blue_bg class after init',
goog.dom.classlist.contains(event.currentTarget, 'blue_bg'));
}
/**
* Test the effect of hovering over a list.
*
* Check that after the MOUSEOVER browser event these classes are added to
* the current target of the event.
*/
function testAddDragItemHoverClasses() {
dlg.handleDragItemMouseover_(event);
assertTrue('Should have cursor_move class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'cursor_move'));
assertTrue('Should have opacity_40 class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'opacity_40'));
assertFalse('Should not have cursor_pointer class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'cursor_pointer'));
assertFalse('Should not have blue_bg class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'blue_bg'));
}
function testAddDragItemHandleHoverClasses() {
dlg.handleDragItemHandleMouseover_(event);
assertFalse('Should not have cursor_move class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'cursor_move'));
assertTrue('Should have opacity_40 class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'opacity_40'));
assertTrue('Should have cursor_pointer class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'cursor_pointer'));
assertFalse('Should not have blue_bg class after MOUSEOVER',
goog.dom.classlist.contains(event.currentTarget, 'blue_bg'));
}
/**
* Test the effect of stopping hovering over a list.
*
* Check that after the MOUSEOUT browser event all CSS classes are removed
* from the target (as we are no longer hovering over the it).
*/
function testRemoveDragItemHoverClasses() {
dlg.handleDragItemMouseover_(event);
dlg.handleDragItemMouseout_(event);
assertFalse('Should have no cursor_move class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'cursor_move'));
assertFalse('Should have no cursor_pointer class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'cursor_pointer'));
assertFalse('Should have no opacity_40 class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'opacity_40'));
assertFalse('Should have no blue_bg class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'blue_bg'));
}
function testRemoveDragItemHandleHoverClasses() {
dlg.handleDragItemHandleMouseover_(event);
dlg.handleDragItemHandleMouseout_(event);
assertFalse('Should have no cursor_move class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'cursor_move'));
assertFalse('Should have no cursor_pointer class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'cursor_pointer'));
assertFalse('Should have no opacity_40 class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'opacity_40'));
assertFalse('Should have no blue_bg class after MOUSEOUT',
goog.dom.classlist.contains(event.currentTarget, 'blue_bg'));
}
/**
* Test the effect of dragging an item. (DRAGSTART event.)
*
* Check that after the MOUSEDOWN browser event is handled by the
* handlePotentialDragStart_() method the currDragItem has the CSS classes
* set by the setter method.
*/
function testAddCurrentDragItemClasses() {
var be = new goog.events.BrowserEvent({
type: goog.events.EventType.MOUSEDOWN,
button: goog.events.BrowserFeature.HAS_W3C_BUTTON ? 0 : 1
});
event.event_ = be;
dlg.handlePotentialDragStart_(event);
assertFalse('Should have no cursor_move class after MOUSEDOWN',
goog.dom.classlist.contains(dlg.currDragItem_, 'cursor_move'));
assertFalse('Should have no cursor_pointer class after MOUSEDOWN',
goog.dom.classlist.contains(dlg.currDragItem_, 'cursor_pointer'));
assertTrue('Should have opacity_40 class after MOUSEDOWN',
goog.dom.classlist.contains(dlg.currDragItem_, 'opacity_40'));
assertTrue('Should have blue_bg class after MOUSEDOWN',
goog.dom.classlist.contains(dlg.currDragItem_, 'blue_bg'));
}
/**
* Test the effect of dragging an item. (DRAGEND event.)
*
* Check that after the MOUSEUP browser event handled by the handleDragEnd_()
* method the currDragItem has no CSS classes set in the dispatched event.
*/
function testRemoveCurrentDragItemClasses() {
var be = new goog.events.BrowserEvent({
type: goog.events.EventType.MOUSEDOWN,
button: goog.events.BrowserFeature.HAS_W3C_BUTTON ? 0 : 1
});
event.event_ = be;
dlg.handlePotentialDragStart_(event);
// Need to catch the dispatched event because the temporary fields
// including dlg.currentDragItem_ are cleared after the dragging has ended.
var currDragItem = goog.dom.createDom(
'div', ['cursor_move', 'blue_bg'], goog.dom.createTextNode('4'));
goog.events.listen(dlg, goog.fx.DragListGroup.EventType.DRAGEND,
function(e) {currDragItem = dlg.currDragItem_;});
var dragger = new goog.fx.Dragger(event.currentTarget);
be.type = goog.events.EventType.MOUSEUP;
be.clientX = 1;
be.clientY = 2;
var dragEvent = new goog.fx.DragEvent(
goog.fx.Dragger.EventType.END, dragger, be.clientX, be.clientY, be);
dlg.handleDragEnd_(dragEvent); // this method dispatches the DRAGEND event
dragger.dispose();
assertFalse('Should have no cursor_move class after MOUSEUP',
goog.dom.classlist.contains(currDragItem, 'cursor_move'));
assertFalse('Should have no cursor_pointer class after MOUSEUP',
goog.dom.classlist.contains(currDragItem, 'cursor_pointer'));
assertFalse('Should have no opacity_40 class after MOUSEUP',
goog.dom.classlist.contains(currDragItem, 'opacity_40'));
assertFalse('Should have no blue_bg class after MOUSEUP',
goog.dom.classlist.contains(currDragItem, 'blue_bg'));
}
/**
* Asserts that the DragListGroup is in idle state.
* @param {!goog.fx.DragListGroup} dlg The DragListGroup to examine.
*/
function assertIdle(dlg) {
assertNull('dragger element has been cleaned up', dlg.draggerEl_);
assertNull('dragger has been cleaned up', dlg.dragger_);
assertEquals('the additional event listeners have been removed',
initialListenerCount, goog.object.getCount(dlg.eventHandler_.keys_));
}
function testFiredEvents() {
goog.testing.events.fireClickSequence(list.firstChild);
assertArrayEquals('event types in case of zero distance dragging', [
goog.fx.DragListGroup.EventType.BEFOREDRAGSTART,
goog.fx.DragListGroup.EventType.DRAGSTART,
goog.fx.DragListGroup.EventType.BEFOREDRAGEND,
goog.fx.DragListGroup.EventType.DRAGEND
], firedEventTypes);
assertIdle(dlg);
}
function testFiredEventsWithHysteresis() {
dlg.setHysteresis(2);
goog.testing.events.fireClickSequence(list.firstChild);
assertArrayEquals('no events fired on click if hysteresis is enabled', [],
firedEventTypes);
assertIdle(dlg);
goog.testing.events.fireMouseDownEvent(list.firstChild, null,
new goog.math.Coordinate(0, 0));
goog.testing.events.fireMouseMoveEvent(list.firstChild,
new goog.math.Coordinate(1, 0));
assertArrayEquals('no events fired below hysteresis distance', [],
firedEventTypes);
goog.testing.events.fireMouseMoveEvent(list.firstChild,
new goog.math.Coordinate(3, 0));
assertArrayEquals('start+move events are fired over hysteresis distance', [
goog.fx.DragListGroup.EventType.BEFOREDRAGSTART,
goog.fx.DragListGroup.EventType.DRAGSTART,
goog.fx.DragListGroup.EventType.BEFOREDRAGMOVE,
goog.fx.DragListGroup.EventType.DRAGMOVE
], firedEventTypes);
firedEventTypes.length = 0;
goog.testing.events.fireMouseUpEvent(list.firstChild, null,
new goog.math.Coordinate(3, 0));
assertArrayEquals('end events are fired on mouseup', [
goog.fx.DragListGroup.EventType.BEFOREDRAGEND,
goog.fx.DragListGroup.EventType.DRAGEND
], firedEventTypes);
assertIdle(dlg);
}
function testPreventDefaultBeforeDragStart() {
goog.events.listen(dlg, goog.fx.DragListGroup.EventType.BEFOREDRAGSTART,
goog.events.Event.preventDefault);
goog.testing.events.fireMouseDownEvent(list.firstChild);
assertArrayEquals('event types if dragging is prevented',
[goog.fx.DragListGroup.EventType.BEFOREDRAGSTART], firedEventTypes);
assertIdle(dlg);
}
function testPreventDefaultBeforeDragStartWithHysteresis() {
dlg.setHysteresis(5);
goog.events.listen(dlg, goog.fx.DragListGroup.EventType.BEFOREDRAGSTART,
goog.events.Event.preventDefault);
goog.testing.events.fireMouseDownEvent(list.firstChild, null,
new goog.math.Coordinate(0, 0));
goog.testing.events.fireMouseMoveEvent(list.firstChild,
new goog.math.Coordinate(10, 0));
assertArrayEquals('event types if dragging is prevented',
[goog.fx.DragListGroup.EventType.BEFOREDRAGSTART], firedEventTypes);
assertIdle(dlg);
}
function testRightClick() {
goog.testing.events.fireMouseDownEvent(list.firstChild,
goog.events.BrowserEvent.MouseButton.RIGHT);
goog.testing.events.fireMouseUpEvent(list.firstChild,
goog.events.BrowserEvent.MouseButton.RIGHT);
assertArrayEquals('no events fired', [], firedEventTypes);
assertIdle(dlg);
}
/**
* Tests that a new item can be added to a drag list after the control has
* been initialized.
*/
function testAddItemToDragList() {
var item =
goog.dom.createDom('div', null, goog.dom.createTextNode('newItem'));
dlg.addItemToDragList(list, item);
assertEquals(item, list.lastChild);
assertEquals(4, goog.dom.getChildren(list).length);
goog.events.listen(dlg, goog.fx.DragListGroup.EventType.BEFOREDRAGSTART,
goog.events.Event.preventDefault);
goog.testing.events.fireMouseDownEvent(item);
assertTrue('Should fire beforedragstart event when clicked',
goog.array.equals([goog.fx.DragListGroup.EventType.BEFOREDRAGSTART],
firedEventTypes));
}
/**
* Tests that a new item added to a drag list after the control has been
* initialized is inserted at the correct position.
*/
function testInsertItemInDragList() {
var item =
goog.dom.createDom('div', null, goog.dom.createTextNode('newItem'));
dlg.addItemToDragList(list, item, 0);
assertEquals(item, list.firstChild);
assertEquals(4, goog.dom.getChildren(list).length);
goog.events.listen(dlg, goog.fx.DragListGroup.EventType.BEFOREDRAGSTART,
goog.events.Event.preventDefault);
goog.testing.events.fireMouseDownEvent(item);
assertTrue('Should fire beforedragstart event when clicked',
goog.array.equals([goog.fx.DragListGroup.EventType.BEFOREDRAGSTART],
firedEventTypes));
}
@@ -0,0 +1,300 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Class to support scrollable containers for drag and drop.
*
* @author dgajda@google.com (Damian Gajda)
*/
goog.provide('goog.fx.DragScrollSupport');
goog.require('goog.Disposable');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.math.Coordinate');
goog.require('goog.style');
/**
* A scroll support class. Currently this class will automatically scroll
* a scrollable container node and scroll it by a fixed amount at a timed
* interval when the mouse is moved above or below the container or in vertical
* margin areas. Intended for use in drag and drop. This could potentially be
* made more general and could support horizontal scrolling.
*
* @param {Element} containerNode A container that can be scrolled.
* @param {number=} opt_margin Optional margin to use while scrolling.
* @param {boolean=} opt_externalMouseMoveTracking Whether mouse move events
* are tracked externally by the client object which calls the mouse move
* event handler, useful when events are generated for more than one source
* element and/or are not real mousemove events.
* @constructor
* @extends {goog.Disposable}
* @see ../demos/dragscrollsupport.html
*/
goog.fx.DragScrollSupport = function(containerNode, opt_margin,
opt_externalMouseMoveTracking) {
goog.Disposable.call(this);
/**
* The container to be scrolled.
* @type {Element}
* @private
*/
this.containerNode_ = containerNode;
/**
* Scroll timer that will scroll the container until it is stopped.
* It will scroll when the mouse is outside the scrolling area of the
* container.
*
* @type {goog.Timer}
* @private
*/
this.scrollTimer_ = new goog.Timer(goog.fx.DragScrollSupport.TIMER_STEP_);
/**
* EventHandler used to set up and tear down listeners.
* @type {goog.events.EventHandler<!goog.fx.DragScrollSupport>}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
/**
* The current scroll delta.
* @type {goog.math.Coordinate}
* @private
*/
this.scrollDelta_ = new goog.math.Coordinate();
/**
* The container bounds.
* @type {goog.math.Rect}
* @private
*/
this.containerBounds_ = goog.style.getBounds(containerNode);
/**
* The margin for triggering a scroll.
* @type {number}
* @private
*/
this.margin_ = opt_margin || 0;
/**
* The bounding rectangle which if left triggers scrolling.
* @type {goog.math.Rect}
* @private
*/
this.scrollBounds_ = opt_margin ?
this.constrainBounds_(this.containerBounds_.clone()) :
this.containerBounds_;
this.setupListeners_(!!opt_externalMouseMoveTracking);
};
goog.inherits(goog.fx.DragScrollSupport, goog.Disposable);
/**
* The scroll timer step in ms.
* @type {number}
* @private
*/
goog.fx.DragScrollSupport.TIMER_STEP_ = 50;
/**
* The scroll step in pixels.
* @type {number}
* @private
*/
goog.fx.DragScrollSupport.SCROLL_STEP_ = 8;
/**
* The suggested scrolling margin.
* @type {number}
*/
goog.fx.DragScrollSupport.MARGIN = 32;
/**
* Whether scrolling should be constrained to happen only when the cursor is
* inside the container node.
* @type {boolean}
* @private
*/
goog.fx.DragScrollSupport.prototype.constrainScroll_ = false;
/**
* Whether horizontal scrolling is allowed.
* @type {boolean}
* @private
*/
goog.fx.DragScrollSupport.prototype.horizontalScrolling_ = true;
/**
* Sets whether scrolling should be constrained to happen only when the cursor
* is inside the container node.
* NOTE: If a margin is not set, then it does not make sense to
* contain the scroll, because in that case scroll will never be triggered.
* @param {boolean} constrain Whether scrolling should be constrained to happen
* only when the cursor is inside the container node.
*/
goog.fx.DragScrollSupport.prototype.setConstrainScroll = function(constrain) {
this.constrainScroll_ = !!this.margin_ && constrain;
};
/**
* Sets whether horizontal scrolling is allowed.
* @param {boolean} scrolling Whether horizontal scrolling is allowed.
*/
goog.fx.DragScrollSupport.prototype.setHorizontalScrolling =
function(scrolling) {
this.horizontalScrolling_ = scrolling;
};
/**
* Constrains the container bounds with respect to the margin.
*
* @param {goog.math.Rect} bounds The container element.
* @return {goog.math.Rect} The bounding rectangle used to calculate scrolling
* direction.
* @private
*/
goog.fx.DragScrollSupport.prototype.constrainBounds_ = function(bounds) {
var margin = this.margin_;
if (margin) {
var quarterHeight = bounds.height * 0.25;
var yMargin = Math.min(margin, quarterHeight);
bounds.top += yMargin;
bounds.height -= 2 * yMargin;
var quarterWidth = bounds.width * 0.25;
var xMargin = Math.min(margin, quarterWidth);
bounds.top += xMargin;
bounds.height -= 2 * xMargin;
}
return bounds;
};
/**
* Attaches listeners and activates automatic scrolling.
* @param {boolean} externalMouseMoveTracking Whether to enable internal
* mouse move event handling.
* @private
*/
goog.fx.DragScrollSupport.prototype.setupListeners_ = function(
externalMouseMoveTracking) {
if (!externalMouseMoveTracking) {
// Track mouse pointer position to determine scroll direction.
this.eventHandler_.listen(goog.dom.getOwnerDocument(this.containerNode_),
goog.events.EventType.MOUSEMOVE, this.onMouseMove);
}
// Scroll with a constant speed.
this.eventHandler_.listen(this.scrollTimer_, goog.Timer.TICK, this.onTick_);
};
/**
* Handler for timer tick event, scrolls the container by one scroll step if
* needed.
* @param {goog.events.Event} event Timer tick event.
* @private
*/
goog.fx.DragScrollSupport.prototype.onTick_ = function(event) {
this.containerNode_.scrollTop += this.scrollDelta_.y;
this.containerNode_.scrollLeft += this.scrollDelta_.x;
};
/**
* Handler for mouse moves events.
* @param {goog.events.Event} event Mouse move event.
*/
goog.fx.DragScrollSupport.prototype.onMouseMove = function(event) {
var deltaX = this.horizontalScrolling_ ? this.calculateScrollDelta(
event.clientX, this.scrollBounds_.left, this.scrollBounds_.width) : 0;
var deltaY = this.calculateScrollDelta(event.clientY,
this.scrollBounds_.top, this.scrollBounds_.height);
this.scrollDelta_.x = deltaX;
this.scrollDelta_.y = deltaY;
// If the scroll data is 0 or the event fired outside of the
// bounds of the container node.
if ((!deltaX && !deltaY) ||
(this.constrainScroll_ &&
!this.isInContainerBounds_(event.clientX, event.clientY))) {
this.scrollTimer_.stop();
} else if (!this.scrollTimer_.enabled) {
this.scrollTimer_.start();
}
};
/**
* Gets whether the input coordinate is in the container bounds.
* @param {number} x The x coordinate.
* @param {number} y The y coordinate.
* @return {boolean} Whether the input coordinate is in the container bounds.
* @private
*/
goog.fx.DragScrollSupport.prototype.isInContainerBounds_ = function(x, y) {
var containerBounds = this.containerBounds_;
return containerBounds.left <= x &&
containerBounds.left + containerBounds.width >= x &&
containerBounds.top <= y &&
containerBounds.top + containerBounds.height >= y;
};
/**
* Calculates scroll delta.
*
* @param {number} coordinate Current mouse pointer coordinate.
* @param {number} min The coordinate value below which scrolling up should be
* started.
* @param {number} rangeLength The length of the range in which scrolling should
* be disabled and above which scrolling down should be started.
* @return {number} The calculated scroll delta.
* @protected
*/
goog.fx.DragScrollSupport.prototype.calculateScrollDelta = function(
coordinate, min, rangeLength) {
var delta = 0;
if (coordinate < min) {
delta = -goog.fx.DragScrollSupport.SCROLL_STEP_;
} else if (coordinate > min + rangeLength) {
delta = goog.fx.DragScrollSupport.SCROLL_STEP_;
}
return delta;
};
/** @override */
goog.fx.DragScrollSupport.prototype.disposeInternal = function() {
goog.fx.DragScrollSupport.superClass_.disposeInternal.call(this);
this.eventHandler_.dispose();
this.scrollTimer_.dispose();
};
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: dgajda@google.com (Damian Gajda)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx.DragScrollSupport
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fx.DragScrollSupportTest');
</script>
<style>
#vContainerDiv {
position: absolute;
top: 20px;
overflow-y: scroll;
width: 100px;
height: 100px;
visibility: hidden;
}
#vContentDiv {
height: 200px;
}
#hContainerDiv {
position: absolute;
top: 20px;
left: 200px;
overflow-x: scroll;
width: 100px;
height: 100px;
visibility: hidden;
}
#hContentDiv {
width: 200px;
}
</style>
</head>
<body>
<div id="vContainerDiv">
<div id="vContentDiv">
Sample text
</div>
</div>
<div id="hContainerDiv">
<div id="hContentDiv">
Sample text
</div>
</div>
</body>
</html>
@@ -0,0 +1,315 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.DragScrollSupportTest');
goog.setTestOnly('goog.fx.DragScrollSupportTest');
goog.require('goog.fx.DragScrollSupport');
goog.require('goog.math.Coordinate');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
var vContainerDiv;
var vContentDiv;
var hContainerDiv;
var hContentDiv;
var clock;
function setUpPage() {
vContainerDiv = document.getElementById('vContainerDiv');
vContentDiv = document.getElementById('vContentDiv');
hContainerDiv = document.getElementById('hContainerDiv');
hContentDiv = document.getElementById('hContentDiv');
}
function setUp() {
clock = new goog.testing.MockClock(true);
}
function tearDown() {
clock.dispose();
}
function testDragZeroMarginDivVContainer() {
var dsc = new goog.fx.DragScrollSupport(vContainerDiv);
// Set initial scroll state.
var scrollTop = 50;
vContainerDiv.scrollTop = scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the vContainer should not trigger scrolling.',
scrollTop, vContainerDiv.scrollTop);
assertEquals('Scroll timer should not tick yet', 0, clock.getTimeoutsMade());
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 10));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the vContainer should trigger scrolling up.',
scrollTop > vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the vContainer should trigger scrolling up.',
scrollTop > vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 110));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing below the vContainer should trigger scrolling down.',
scrollTop < vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing below the vContainer should trigger scrolling down.',
scrollTop < vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the vContainer should stop scrolling.',
scrollTop, vContainerDiv.scrollTop);
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
dsc.dispose();
}
function testDragZeroMarginDivHContainer() {
var dsc = new goog.fx.DragScrollSupport(hContainerDiv);
// Set initial scroll state.
var scrollLeft = 50;
hContainerDiv.scrollLeft = scrollLeft;
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 + 50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the hContainer should not trigger scrolling.',
scrollLeft, hContainerDiv.scrollLeft);
assertEquals('Scroll timer should not tick yet', 0, clock.getTimeoutsMade());
scrollLeft = hContainerDiv.scrollLeft;
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 - 10, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing left of the hContainer should trigger scrolling left.',
scrollLeft > hContainerDiv.scrollLeft);
scrollLeft = hContainerDiv.scrollLeft;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing left of the hContainer should trigger scrolling left.',
scrollLeft > hContainerDiv.scrollLeft);
scrollLeft = hContainerDiv.scrollLeft;
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 + 110, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing right of the hContainer should trigger scrolling right.',
scrollLeft < hContainerDiv.scrollLeft);
scrollLeft = hContainerDiv.scrollLeft;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing right of the hContainer should trigger scrolling right.',
scrollLeft < hContainerDiv.scrollLeft);
scrollLeft = hContainerDiv.scrollLeft;
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 + 50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the hContainer should stop scrolling.',
scrollLeft, hContainerDiv.scrollLeft);
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
dsc.dispose();
}
function testDragMarginDivVContainer() {
var dsc = new goog.fx.DragScrollSupport(vContainerDiv, 20);
// Set initial scroll state.
var scrollTop = 50;
vContainerDiv.scrollTop = scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the vContainer should not trigger scrolling.',
scrollTop, vContainerDiv.scrollTop);
assertEquals('Scroll timer should not tick yet', 0, clock.getTimeoutsMade());
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 30));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the margin should trigger scrolling up.',
scrollTop > vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the margin should trigger scrolling up.',
scrollTop > vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 90));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing below the margin should trigger scrolling down.',
scrollTop < vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the margin should trigger scrolling down.',
scrollTop < vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the margin should stop scrolling.',
scrollTop, vContainerDiv.scrollTop);
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Scroll timer should have ticked 5 times',
5, clock.getTimeoutsMade());
dsc.dispose();
}
function testDragMarginScrollConstrainedDivVContainer() {
var dsc = new goog.fx.DragScrollSupport(vContainerDiv, 20);
dsc.setConstrainScroll(true);
// Set initial scroll state.
var scrollTop = 50;
vContainerDiv.scrollTop = scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the vContainer should not trigger scrolling.',
scrollTop, vContainerDiv.scrollTop);
assertEquals('Scroll timer should not tick yet', 0, clock.getTimeoutsMade());
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 30));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the margin should trigger scrolling up.',
scrollTop > vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the margin should trigger scrolling up.',
scrollTop > vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 90));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing below the margin should trigger scrolling down.',
scrollTop < vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing above the margin should trigger scrolling down.',
scrollTop < vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing inside the margin should stop scrolling.',
scrollTop, vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 10));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing above the vContainer should not trigger scrolling up.',
scrollTop, vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing above the vContainer should not trigger scrolling up.',
scrollTop, vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(50, 20 + 110));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals(
'Mousing below the vContainer should not trigger scrolling down.',
scrollTop, vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals(
'Mousing below the vContainer should not trigger scrolling down.',
scrollTop, vContainerDiv.scrollTop);
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
scrollTop = vContainerDiv.scrollTop;
goog.testing.events.fireMouseMoveEvent(vContainerDiv,
new goog.math.Coordinate(150, 20 + 90));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing to the right of the vContainer should not trigger ' +
'scrolling up.', scrollTop, vContainerDiv.scrollTop);
scrollTop = vContainerDiv.scrollTop;
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Mousing to the right of the vContainer should not trigger ' +
'scrolling up.', scrollTop, vContainerDiv.scrollTop);
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Scroll timer should have ticked 5 times',
5, clock.getTimeoutsMade());
dsc.dispose();
}
function testSetHorizontalScrolling() {
var dsc = new goog.fx.DragScrollSupport(hContainerDiv);
dsc.setHorizontalScrolling(false);
// Set initial scroll state.
var scrollLeft = 50;
hContainerDiv.scrollLeft = scrollLeft;
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 - 10, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Horizontal scrolling should be turned off',
0, clock.getTimeoutsMade());
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 + 110, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertEquals('Horizontal scrolling should be turned off',
0, clock.getTimeoutsMade());
dsc.setHorizontalScrolling(true);
scrollLeft = hContainerDiv.scrollLeft;
goog.testing.events.fireMouseMoveEvent(hContainerDiv,
new goog.math.Coordinate(200 - 10, 20 + 50));
clock.tick(goog.fx.DragScrollSupport.TIMER_STEP_ + 1);
assertTrue('Mousing left of the hContainer should trigger scrolling left.',
scrollLeft > hContainerDiv.scrollLeft);
dsc.dispose();
}
@@ -0,0 +1,85 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Easing functions for animations.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.fx.easing');
/**
* Ease in - Start slow and speed up.
* @param {number} t Input between 0 and 1.
* @return {number} Output between 0 and 1.
*/
goog.fx.easing.easeIn = function(t) {
return goog.fx.easing.easeInInternal_(t, 3);
};
/**
* Ease in with specifiable exponent.
* @param {number} t Input between 0 and 1.
* @param {number} exp Ease exponent.
* @return {number} Output between 0 and 1.
* @private
*/
goog.fx.easing.easeInInternal_ = function(t, exp) {
return Math.pow(t, exp);
};
/**
* Ease out - Start fastest and slows to a stop.
* @param {number} t Input between 0 and 1.
* @return {number} Output between 0 and 1.
*/
goog.fx.easing.easeOut = function(t) {
return goog.fx.easing.easeOutInternal_(t, 3);
};
/**
* Ease out with specifiable exponent.
* @param {number} t Input between 0 and 1.
* @param {number} exp Ease exponent.
* @return {number} Output between 0 and 1.
* @private
*/
goog.fx.easing.easeOutInternal_ = function(t, exp) {
return 1 - goog.fx.easing.easeInInternal_(1 - t, exp);
};
/**
* Ease out long - Start fastest and slows to a stop with a long ease.
* @param {number} t Input between 0 and 1.
* @return {number} Output between 0 and 1.
*/
goog.fx.easing.easeOutLong = function(t) {
return goog.fx.easing.easeOutInternal_(t, 4);
};
/**
* Ease in and out - Start slow, speed up, then slow down.
* @param {number} t Input between 0 and 1.
* @return {number} Output between 0 and 1.
*/
goog.fx.easing.inAndOut = function(t) {
return 3 * t * t - 2 * t * t * t;
};
@@ -0,0 +1,33 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fx.easingTest');
goog.setTestOnly('goog.fx.easingTest');
goog.require('goog.fx.easing');
goog.require('goog.testing.jsunit');
function testEaseIn() {
assertEquals(0, goog.fx.easing.easeIn(0));
assertEquals(1, goog.fx.easing.easeIn(1));
assertRoughlyEquals(Math.pow(0.5, 3), goog.fx.easing.easeIn(0.5), 0.01);
}
function testEaseOut() {
assertEquals(0, goog.fx.easing.easeOut(0));
assertEquals(1, goog.fx.easing.easeOut(1));
assertRoughlyEquals(1 - Math.pow(0.5, 3), goog.fx.easing.easeOut(0.5), 0.01);
}
@@ -0,0 +1,32 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Legacy stub for the goog.fx namespace. Requires the moved
* namespaces. Animation and easing have been moved to animation.js and
* easing.js. Users of this stub should move off so we may remove it in the
* future.
*
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.fx');
goog.require('goog.asserts');
goog.require('goog.fx.Animation');
goog.require('goog.fx.Animation.EventType');
goog.require('goog.fx.Animation.State');
goog.require('goog.fx.AnimationEvent');
goog.require('goog.fx.Transition.EventType');
goog.require('goog.fx.easing');
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: arv@google.com (Erik Arvidsson)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.fx
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.fxTest');
</script>
<style>
</style>
</head>
<body>
</body>
</html>
@@ -0,0 +1,86 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.fxTest');
goog.setTestOnly('goog.fxTest');
goog.require('goog.fx.Animation');
goog.require('goog.object');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
// TODO(arv): Add tests for the event dispatches.
// TODO(arv): Add tests for the calculation of the coordinates.
var clock, replacer, anim, anim2;
var Animation = goog.fx.Animation;
function setUpPage() {
clock = new goog.testing.MockClock(true);
}
function tearDownPage() {
clock.dispose();
}
function setUp() {
replacer = new goog.testing.PropertyReplacer();
}
function tearDown() {
replacer.reset();
if (anim && anim.dispose) {
anim.dispose();
}
if (anim2 && anim2.dispose) {
anim2.dispose();
}
}
function testAnimationConstructor() {
assertThrows('Should throw since first arg is not an array', function() {
new Animation(1, [2], 3);
});
assertThrows('Should throw since second arg is not an array', function() {
new Animation([1], 2, 3);
});
assertThrows('Should throw since the length are different', function() {
new Animation([0, 1], [2], 3);
});
}
function testPlayAndStopDoesNotLeaveAnyActiveAnimations() {
anim = new Animation([0], [1], 1000);
assertTrue('There should be no active animations',
goog.object.isEmpty(goog.fx.anim.activeAnimations_));
anim.play();
assertEquals('There should be one active animations',
1, goog.object.getCount(goog.fx.anim.activeAnimations_));
anim.stop();
assertTrue('There should be no active animations',
goog.object.isEmpty(goog.fx.anim.activeAnimations_));
anim.play();
assertEquals('There should be one active animations',
1, goog.object.getCount(goog.fx.anim.activeAnimations_));
anim.pause();
assertTrue('There should be no active animations',
goog.object.isEmpty(goog.fx.anim.activeAnimations_));
}
@@ -0,0 +1,76 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An interface for transition animation. This is a simple
* interface that allows for playing and stopping a transition. It adds
* a simple event model with BEGIN and END event.
*
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.fx.Transition');
goog.provide('goog.fx.Transition.EventType');
/**
* An interface for programmatic transition. Must extend
* {@code goog.events.EventTarget}.
* @interface
*/
goog.fx.Transition = function() {};
/**
* Transition event types.
* @enum {string}
*/
goog.fx.Transition.EventType = {
/** Dispatched when played for the first time OR when it is resumed. */
PLAY: 'play',
/** Dispatched only when the animation starts from the beginning. */
BEGIN: 'begin',
/** Dispatched only when animation is restarted after a pause. */
RESUME: 'resume',
/**
* Dispatched when animation comes to the end of its duration OR stop
* is called.
*/
END: 'end',
/** Dispatched only when stop is called. */
STOP: 'stop',
/** Dispatched only when animation comes to its end naturally. */
FINISH: 'finish',
/** Dispatched when an animation is paused. */
PAUSE: 'pause'
};
/**
* Plays the transition.
*/
goog.fx.Transition.prototype.play;
/**
* Stops the transition.
*/
goog.fx.Transition.prototype.stop;
@@ -0,0 +1,236 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An abstract base class for transitions. This is a simple
* interface that allows for playing, pausing and stopping an animation. It adds
* a simple event model, and animation status.
*/
goog.provide('goog.fx.TransitionBase');
goog.provide('goog.fx.TransitionBase.State');
goog.require('goog.events.EventTarget');
goog.require('goog.fx.Transition'); // Unreferenced: interface
/**
* Constructor for a transition object.
*
* @constructor
* @implements {goog.fx.Transition}
* @extends {goog.events.EventTarget}
*/
goog.fx.TransitionBase = function() {
goog.fx.TransitionBase.base(this, 'constructor');
/**
* The internal state of the animation.
* @type {goog.fx.TransitionBase.State}
* @private
*/
this.state_ = goog.fx.TransitionBase.State.STOPPED;
/**
* Timestamp for when the animation was started.
* @type {?number}
* @protected
*/
this.startTime = null;
/**
* Timestamp for when the animation finished or was stopped.
* @type {?number}
* @protected
*/
this.endTime = null;
};
goog.inherits(goog.fx.TransitionBase, goog.events.EventTarget);
/**
* Enum for the possible states of an animation.
* @enum {number}
*/
goog.fx.TransitionBase.State = {
STOPPED: 0,
PAUSED: -1,
PLAYING: 1
};
/**
* Plays the animation.
*
* @param {boolean=} opt_restart Optional parameter to restart the animation.
* @return {boolean} True iff the animation was started.
* @override
*/
goog.fx.TransitionBase.prototype.play = goog.abstractMethod;
/**
* Stops the animation.
*
* @param {boolean=} opt_gotoEnd Optional boolean parameter to go the the end of
* the animation.
* @override
*/
goog.fx.TransitionBase.prototype.stop = goog.abstractMethod;
/**
* Pauses the animation.
*/
goog.fx.TransitionBase.prototype.pause = goog.abstractMethod;
/**
* Returns the current state of the animation.
* @return {goog.fx.TransitionBase.State} State of the animation.
*/
goog.fx.TransitionBase.prototype.getStateInternal = function() {
return this.state_;
};
/**
* Sets the current state of the animation to playing.
* @protected
*/
goog.fx.TransitionBase.prototype.setStatePlaying = function() {
this.state_ = goog.fx.TransitionBase.State.PLAYING;
};
/**
* Sets the current state of the animation to paused.
* @protected
*/
goog.fx.TransitionBase.prototype.setStatePaused = function() {
this.state_ = goog.fx.TransitionBase.State.PAUSED;
};
/**
* Sets the current state of the animation to stopped.
* @protected
*/
goog.fx.TransitionBase.prototype.setStateStopped = function() {
this.state_ = goog.fx.TransitionBase.State.STOPPED;
};
/**
* @return {boolean} True iff the current state of the animation is playing.
*/
goog.fx.TransitionBase.prototype.isPlaying = function() {
return this.state_ == goog.fx.TransitionBase.State.PLAYING;
};
/**
* @return {boolean} True iff the current state of the animation is paused.
*/
goog.fx.TransitionBase.prototype.isPaused = function() {
return this.state_ == goog.fx.TransitionBase.State.PAUSED;
};
/**
* @return {boolean} True iff the current state of the animation is stopped.
*/
goog.fx.TransitionBase.prototype.isStopped = function() {
return this.state_ == goog.fx.TransitionBase.State.STOPPED;
};
/**
* Dispatches the BEGIN event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onBegin = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.BEGIN);
};
/**
* Dispatches the END event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onEnd = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.END);
};
/**
* Dispatches the FINISH event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onFinish = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.FINISH);
};
/**
* Dispatches the PAUSE event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onPause = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.PAUSE);
};
/**
* Dispatches the PLAY event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onPlay = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.PLAY);
};
/**
* Dispatches the RESUME event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onResume = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.RESUME);
};
/**
* Dispatches the STOP event. Sub classes should override this instead
* of listening to the event, and call this instead of dispatching the event.
* @protected
*/
goog.fx.TransitionBase.prototype.onStop = function() {
this.dispatchAnimationEvent(goog.fx.Transition.EventType.STOP);
};
/**
* Dispatches an event object for the current animation.
* @param {string} type Event type that will be dispatched.
* @protected
*/
goog.fx.TransitionBase.prototype.dispatchAnimationEvent = function(type) {
this.dispatchEvent(type);
};