diff --git a/ScrollableContainer.js b/ScrollableContainer.js
new file mode 100755
index 0000000000..b5b7c67c22
--- /dev/null
+++ b/ScrollableContainer.js
@@ -0,0 +1,29 @@
+define([
+ "delite/register",
+ "delite/Widget",
+ "delite/Container",
+ "delite/Scrollable"
+], function (register, Widget, Container, Scrollable) {
+
+ // module:
+ // deliteful/ScrollableContainer
+
+ return register("d-scrollable-container", [HTMLElement, Widget, Container, Scrollable], {
+ // summary:
+ // A container widget with scrolling capabilities.
+ // description:
+ // A container widget which can scroll its content
+ // horizontally and/or vertically. Its scrolling capabilities
+ // and API are provided by the mixin delite/Scrollable.
+ // example:
+ // |
+ // | ...
+ // | ...
+ // | ...
+ // |
+
+ // baseClass: String
+ // The name of the CSS class of this widget.
+ baseClass: "d-scrollable-container"
+ });
+});
diff --git a/tests/ScrollableContainer-shared.js b/tests/ScrollableContainer-shared.js
new file mode 100755
index 0000000000..70eada7be6
--- /dev/null
+++ b/tests/ScrollableContainer-shared.js
@@ -0,0 +1,209 @@
+define([
+ "intern!object",
+ "intern/chai!assert",
+ "dojo/dom-geometry",
+ "dojo/dom-class"
+], function (registerSuite, assert, domGeom, domClass) {
+
+ // This object is shared by ScrollableContainer tests.
+
+ return {
+ "Default CSS" : function () {
+ var w = document.getElementById("sc1");
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (id='sc1')");
+ assert.isTrue(domClass.contains(w, "d-scrollable"), // class added by the mixin delite/Scrollable
+ "Expecting d-scrollable CSS class! (id='sc1')");
+
+ w = document.getElementById("sc2"); // with scrollDirection == "none"
+ assert.equal(w.scrollDirection, "none", "wrong scroll direction for id=sc2!");
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (id='sc2')");
+ // when scrollDirection is "none", this CSS class should NOT be present:
+ assert.isFalse(domClass.contains(w, "d-scrollable"),
+ "Not expecting d-scrollable CSS class! (id='sc2')");
+
+ w = document.getElementById("mysc1");
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (id='mysc1')");
+ assert.isTrue(domClass.contains(w, "d-scrollable"), // class added by the mixin delite/Scrollable
+ "Expecting d-scrollable CSS class! (id='mysc1')");
+ },
+
+ "CSS class dependency on scrollDirection" : function () {
+ var w = document.getElementById("sc1");
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (id='sc1')");
+ assert.isTrue(domClass.contains(w, "d-scrollable"), // class added by the mixin delite/Scrollable
+ "Expecting d-scrollable CSS class! (id='sc1')");
+
+ w.scrollDirection = "none";
+ w.validateRendering(); // scrollDirection is an invalidating property
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (scrollDirection='none')");
+ // when scrollDirection is "none", this CSS class should NOT be present:
+ assert.isFalse(domClass.contains(w, "d-scrollable"),
+ "Not expecting d-scrollable CSS class! (scrollDirection='none')");
+
+ w.scrollDirection = "vertical"; // set back to "vertical"
+ w.validateRendering();
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (scrollDirection='vertical')");
+ assert.isTrue(domClass.contains(w, "d-scrollable"), // class added by the mixin delite/Scrollable
+ "Expecting d-scrollable CSS class! (scrollDirection='vertical')");
+
+ w.scrollDirection = "horizontal"; // same for "horizontal"
+ w.validateRendering();
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (scrollDirection='horizontal')");
+ assert.isTrue(domClass.contains(w, "d-scrollable"), // class added by the mixin delite/Scrollable
+ "Expecting d-scrollable CSS class! (scrollDirection='horizontal')");
+
+ w.scrollDirection = "both"; // same for "both"
+ w.validateRendering();
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (scrollDirection='both')");
+ assert.isTrue(domClass.contains(w, "d-scrollable"), // class added by the mixin delite/Scrollable
+ "Expecting d-scrollable CSS class! (scrollDirection='both')");
+
+ w.scrollDirection = "none"; // and none again
+ w.validateRendering();
+ assert.isTrue(domClass.contains(w, "d-scrollable-container"),
+ "Expecting d-scrollable-container CSS class! (scrollDirection='none')");
+ // when scrollDirection is "none", this CSS class should NOT be present:
+ assert.isFalse(domClass.contains(w, "d-scrollable"),
+ "Not expecting d-scrollable CSS class! (scrollDirection='none')");
+ },
+
+ "scrollableNode" : function () {
+ var w = document.getElementById("sc1");
+ assert.isTrue(w.scrollableNode === w, "Wrong scrollableNode!");
+ },
+
+ "scrollTop/scrollLeft" : function () {
+ var w = document.getElementById("sc1");
+ w.scrollDirection = "both";
+ w.validateRendering();
+ assert.equal(w.scrollableNode.scrollTop, 0, "scrollTop");
+ assert.equal(w.scrollableNode.scrollLeft, 0, "scrollLeft");
+ },
+
+ "scrollBy" : function () {
+ var w = document.getElementById("sc1");
+ var d = this.async(1000);
+ w.scrollDirection = "both";
+ w.validateRendering();
+ w.scrollBy({x: 10});
+ assert.equal(w.scrollableNode.scrollLeft, 10, "scrollLeft #1");
+ assert.equal(w.scrollableNode.scrollTop, 0, "scrollTop #1");
+ w.scrollBy({y: 10});
+ assert.equal(w.scrollableNode.scrollLeft, 10, "scrollLeft #2");
+ assert.equal(w.scrollableNode.scrollTop, 10, "scrollTop #2");
+ w.scrollBy({x: 10, y: 10});
+ assert.equal(w.scrollableNode.scrollLeft, 20, "scrollLeft #3");
+ assert.equal(w.scrollableNode.scrollTop, 20, "scrollTop #3");
+
+ // Now with animation:
+ w.scrollBy({x: 10, y: 10}, 100/*duration*/);
+ setTimeout(d.callback(function () {
+ assert.equal(w.scrollableNode.scrollLeft, 30, "scrollLeft #4");
+ assert.equal(w.scrollableNode.scrollTop, 30, "scrollTop #4");
+
+ w.scrollBy({x: 10, y: 10}, 0/*duration*/);
+ // when the duration is 0, no animation, thus no need to test asynchronously
+ assert.equal(w.scrollableNode.scrollLeft, 40, "scrollLeft #5");
+ assert.equal(w.scrollableNode.scrollTop, 40, "scrollTop #5");
+ }), 1000);
+
+ return d;
+ },
+
+ "scrollTo" : function () {
+ var w = document.getElementById("sc1");
+ var d = this.async(1000);
+ w.scrollDirection = "both";
+ w.validateRendering();
+ w.scrollTo({x: 10});
+ assert.equal(w.scrollableNode.scrollLeft, 10, "scrollLeft #1");
+ w.scrollTo({y: 10});
+ assert.equal(w.scrollableNode.scrollTop, 10, "scrollTop #1");
+ w.scrollTo({x: 20, y: 20});
+ assert.equal(w.scrollableNode.scrollLeft, 20, "scrollLeft #2");
+ assert.equal(w.scrollableNode.scrollTop, 20, "scrollTop #2");
+
+ // Now with animation:
+ w.scrollTo({x: 30, y: 30}, 100/*duration*/);
+ setTimeout(d.callback(function () {
+ assert.equal(w.scrollableNode.scrollLeft, 30, "scrollLeft #3");
+ assert.equal(w.scrollableNode.scrollTop, 30, "scrollTop #3");
+
+ w.scrollTo({x: 40, y: 40}, 0/*duration*/);
+ // when the duration is 0, no animation, thus no need to test asynchronously
+ assert.equal(w.scrollableNode.scrollLeft, 40, "scrollLeft #4");
+ assert.equal(w.scrollableNode.scrollTop, 40, "scrollTop #4");
+ }), 1000);
+
+ return d;
+ },
+
+ "getCurrentScroll" : function () {
+ var w = document.getElementById("sc1");
+ var pos = {x: 10, y: 10};
+ w.scrollDirection = "both";
+ w.validateRendering();
+ w.scrollTo(pos);
+ assert.deepEqual(w.getCurrentScroll(), pos, "Wrong getCurrentScroll!");
+ },
+
+ "isTop/Bottom/Left/RightScroll" : function () {
+ var w = document.getElementById("sc1");
+ var wContent = document.getElementById("sc1content");
+ var pos = {x: 10, y: 10};
+ var box = domGeom.getMarginBox(wContent);
+ var width = box.w;
+ var height = box.h;
+ w.scrollDirection = "both";
+ w.validateRendering();
+ w.scrollTo(pos);
+ assert.isFalse(w.isTopScroll(), "isTopScroll() #1");
+ assert.isFalse(w.isBottomScroll(), "isBottomScroll() #1");
+ assert.isFalse(w.isRightScroll(), "isRightScroll() #1");
+ assert.isFalse(w.isLeftScroll(), "isLeftScroll() #1");
+
+ pos = {x: 0, y: 10};
+ w.scrollTo(pos);
+ assert.isFalse(w.isTopScroll(), "isTopScroll() #2");
+ assert.isFalse(w.isBottomScroll(), "isBottomScroll() #2");
+ assert.isFalse(w.isRightScroll(), "isRightScroll() #2");
+ assert.isTrue(w.isLeftScroll(), "isLeftScroll() #2");
+
+ pos = {x: 10, y: 0};
+ w.scrollTo(pos);
+ assert.isTrue(w.isTopScroll(), "isTopScroll() #3");
+ assert.isFalse(w.isBottomScroll(), "isBottomScroll() #3");
+ assert.isFalse(w.isRightScroll(), "isRightScroll() #3");
+ assert.isFalse(w.isLeftScroll(), "isLeftScroll() #3");
+
+ pos = {x: width, y: 10};
+ w.scrollTo(pos);
+ assert.isFalse(w.isTopScroll(), "isTopScroll() #4");
+ assert.isFalse(w.isBottomScroll(), "isBottomScroll() #4");
+ assert.isTrue(w.isRightScroll(), "isRightScroll() #4");
+ assert.isFalse(w.isLeftScroll(), "isLeftScroll() #4");
+
+ pos = {x: 10, y: height};
+ w.scrollTo(pos);
+ assert.isFalse(w.isTopScroll(), "isTopScroll() #5");
+ assert.isTrue(w.isBottomScroll(), "isBottomScroll() #5");
+ assert.isFalse(w.isRightScroll(), "isRightScroll() #5");
+ assert.isFalse(w.isLeftScroll(), "isLeftScroll() #5");
+
+ pos = {x: 0, y: 0};
+ w.scrollTo(pos);
+ assert.isTrue(w.isTopScroll(), "isTopScroll() #6");
+ assert.isFalse(w.isBottomScroll(), "isBottomScroll() #6");
+ assert.isFalse(w.isRightScroll(), "isRightScroll() #6");
+ assert.isTrue(w.isLeftScroll(), "isLeftScroll() #6");
+ }
+ };
+});
diff --git a/tests/ScrollableContainer.js b/tests/ScrollableContainer.js
new file mode 100755
index 0000000000..c861af12f2
--- /dev/null
+++ b/tests/ScrollableContainer.js
@@ -0,0 +1,89 @@
+define([
+ "dcl/dcl",
+ "intern!object",
+ "intern/chai!assert",
+ "dojo/dom-class",
+ "delite/register",
+ "delite/Widget",
+ "delite/Scrollable",
+ "deliteful/ScrollableContainer",
+ "./ScrollableContainer-shared"
+], function (dcl, registerSuite, assert, domClass, register, Widget,
+ Scrollable, ScrollableContainer, ScrollableSharedTests) {
+
+ // Note that the actual testing is done in ScrollableContainer-shared.
+
+ var container, MyScrollableWidget, MyScrollableContainer;
+ /*jshint multistr: true */
+ var html = " \
+ \
+ \
+ \
+ \
+ \
+ ";
+
+
+ // Markup use-case
+
+ var suite = {
+ name: "delite/ScrollableContainer: markup",
+ setup: function () {
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ container.innerHTML = html;
+ register("my-scrolable-container", [ScrollableContainer], {});
+ register.parse();
+ },
+ teardown: function () {
+ container.parentNode.removeChild(container);
+ }
+ };
+
+ dcl.mix(suite, ScrollableSharedTests);
+
+ registerSuite(suite);
+
+ // Programatic creation
+
+ suite = {
+ name: "delite/ScrollableContainer: programatic",
+ setup: function () {
+ container = document.createElement("div");
+ document.body.appendChild(container);
+
+ MyScrollableContainer = register("my-sc-prog", [ScrollableContainer], {});
+
+ var w = new ScrollableContainer({ id: "sc1" });
+ w.style.position = "absolute";
+ w.style.width = "200px";
+ w.style.height = "200px";
+ container.appendChild(w);
+ w.startup();
+
+ var innerContent = document.createElement("div");
+ innerContent.id = "sc1content";
+ innerContent.style.width = "2000px";
+ innerContent.style.height = "2000px";
+ w.appendChild(innerContent);
+ w.startup();
+
+ w = new MyScrollableContainer({ id: "mysc1" });
+ container.appendChild(w);
+ w.startup();
+
+ w = new ScrollableContainer({ id: "sc2" });
+ w.scrollDirection = "none";
+ container.appendChild(w);
+ w.startup();
+ },
+ teardown: function () {
+ container.parentNode.removeChild(container);
+ }
+ };
+
+ dcl.mix(suite, ScrollableSharedTests);
+
+ registerSuite(suite);
+});
diff --git a/tests/intern/unit.js b/tests/intern/unit.js
index 7f48721ad4..fe04d5e328 100644
--- a/tests/intern/unit.js
+++ b/tests/intern/unit.js
@@ -1,3 +1,4 @@
// Listing of all the deliteful unit tests
define([
+ "../ScrollableContainer"
]);
diff --git a/tests/test_ScrollableContainer-full-screen.html b/tests/test_ScrollableContainer-full-screen.html
new file mode 100755
index 0000000000..41d830df06
--- /dev/null
+++ b/tests/test_ScrollableContainer-full-screen.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+ ScrollableContainer demo
+
+
+
+
+
+
+
+
+
+
+
+
+ Header
+
+
+
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+
+
+
+ Footer
+
+
+
diff --git a/tests/test_ScrollableContainer-small.html b/tests/test_ScrollableContainer-small.html
new file mode 100755
index 0000000000..16d138a0fb
--- /dev/null
+++ b/tests/test_ScrollableContainer-small.html
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+ ScrollableContainer demo
+
+
+
+
+
+
+
+
+
+
+
+
+ Header 1
+
+
+
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+
+
+
+ Footer 1
+
+
+
+ Header 2
+
+
+
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+
+
+
+ Footer 2
+
+
+
diff --git a/tests/test_ScrollableContainer.html b/tests/test_ScrollableContainer.html
new file mode 100755
index 0000000000..d3b8d78eba
--- /dev/null
+++ b/tests/test_ScrollableContainer.html
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+ ScrollableContainer demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+ - Featured
+ - Categories
+ - Top 25
+ - Search
+ - Updates
+
+
+
+
+