Adding mapbox-gl branch
This commit is contained in:
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;
|
||||
};
|
||||
@@ -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><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);
|
||||
};
|
||||
Reference in New Issue
Block a user