Merge remote-tracking branch 'origin/open920' into open-master

This commit is contained in:
bwyu 2015-03-09 11:06:12 -07:00
commit b05315a140
5 changed files with 195 additions and 0 deletions

View File

@ -0,0 +1,8 @@
# Directives
* `mct-scroll-x` is an attribute whose value is an assignable
Angular expression. This two-way binds that expression to the
horizontal scroll state of the element on which it is applied.
* `mct-scroll-y` is an attribute whose value is an assignable
Angular expression. This two-way binds that expression to the
vertical scroll state of the element on which it is applied.

View File

@ -105,6 +105,34 @@
"key": "mctResize",
"implementation": "directives/MCTResize.js",
"depends": [ "$timeout" ]
},
{
"key": "mctScrollX",
"implementation": "directives/MCTScroll.js",
"depends": [ "$parse", "MCT_SCROLL_X_PROPERTY", "MCT_SCROLL_X_ATTRIBUTE" ]
},
{
"key": "mctScrollY",
"implementation": "directives/MCTScroll.js",
"depends": [ "$parse", "MCT_SCROLL_Y_PROPERTY", "MCT_SCROLL_Y_ATTRIBUTE" ]
}
],
"constants": [
{
"key": "MCT_SCROLL_X_PROPERTY",
"value": "scrollLeft"
},
{
"key": "MCT_SCROLL_X_ATTRIBUTE",
"value": "mctScrollX"
},
{
"key": "MCT_SCROLL_Y_PROPERTY",
"value": "scrollTop"
},
{
"key": "MCT_SCROLL_Y_ATTRIBUTE",
"value": "mctScrollY"
}
],
"containers": [

View File

@ -0,0 +1,62 @@
/*global define*/
define(
[],
function () {
'use strict';
/**
* Implements `mct-scroll-x` and `mct-scroll-y` directives. Listens
* for scroll events and publishes their results into scope; watches
* scope and updates scroll state to match. This varies for x- and y-
* directives only by the attribute name chosen to find the expression,
* and the property (scrollLeft or scrollTop) managed within the
* element.
*
* This is exposed as two directives in `bundle.json`; the difference
* is handled purely by parameterization.
*
* @constructor
* @param $parse Angular's $parse
* @param {string} property property to manage within the HTML element
* @param {string} attribute attribute to look at for the assignable
* Angular expression
*/
function MCTScroll($parse, property, attribute) {
function link(scope, element, attrs) {
var expr = attrs[attribute],
parsed = $parse(expr);
// Set the element's scroll to match the scope's state
function updateElement(value) {
element[0][property] = value;
}
// Handle event; assign to scroll state to scope
function updateScope() {
parsed.assign(scope, element[0][property]);
scope.$apply(expr);
}
// Initialize state in scope
parsed.assign(scope, element[0][property]);
// Update element state when value in scope changes
scope.$watch(expr, updateElement);
// Update state in scope when element is scrolled
element.on('scroll', updateScope);
}
return {
// Restrict to attributes
restrict: "A",
// Use this link function
link: link
};
}
return MCTScroll;
}
);

View File

@ -0,0 +1,96 @@
/*global define,describe,it,expect,jasmine,beforeEach*/
define(
['../../src/directives/MCTScroll'],
function (MCTScroll) {
"use strict";
var EVENT_PROPERTY = "testProperty",
ATTRIBUTE = "testAttribute",
EXPRESSION = "some.expression";
// MCTScroll is the commonality between mct-scroll-x and
// mct-scroll-y; it gets the event property to watch and
// the attribute which contains the associated assignable
// expression.
describe("An mct-scroll-* directive", function () {
var mockParse,
mockParsed,
mockScope,
mockElement,
testAttrs,
mctScroll;
beforeEach(function () {
mockParse = jasmine.createSpy('$parse');
mockParsed = jasmine.createSpy('parsed');
mockParsed.assign = jasmine.createSpy('assign');
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$apply']);
mockElement = [{ testProperty: 42 }];
mockElement.on = jasmine.createSpy('on');
mockParse.andReturn(mockParsed);
testAttrs = {};
testAttrs[ATTRIBUTE] = EXPRESSION;
mctScroll = new MCTScroll(
mockParse,
EVENT_PROPERTY,
ATTRIBUTE
);
mctScroll.link(mockScope, mockElement, testAttrs);
});
it("is available for attributes", function () {
expect(mctScroll.restrict).toEqual('A');
});
it("does not create an isolate scope", function () {
expect(mctScroll.scope).toBeUndefined();
});
it("watches for changes in observed expression", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
EXPRESSION,
jasmine.any(Function)
);
// Should have been only watch (other tests need this to be true)
expect(mockScope.$watch.calls.length).toEqual(1);
});
it("listens for scroll events", function () {
expect(mockElement.on).toHaveBeenCalledWith(
'scroll',
jasmine.any(Function)
);
// Should have been only listener (other tests need this to be true)
expect(mockElement.on.calls.length).toEqual(1);
});
it("publishes initial scroll state", function () {
expect(mockParse).toHaveBeenCalledWith(EXPRESSION);
expect(mockParsed.assign).toHaveBeenCalledWith(mockScope, 42);
});
it("updates scroll state when scope changes", function () {
mockScope.$watch.mostRecentCall.args[1](64);
expect(mockElement[0].testProperty).toEqual(64);
});
it("updates scope when scroll state changes", function () {
mockElement[0].testProperty = 12321;
mockElement.on.mostRecentCall.args[1]({ target: mockElement[0] });
expect(mockParsed.assign).toHaveBeenCalledWith(mockScope, 12321);
expect(mockScope.$apply).toHaveBeenCalledWith(EXPRESSION);
});
// This would trigger an infinite digest exception
it("does not call $apply during construction", function () {
expect(mockScope.$apply).not.toHaveBeenCalled();
});
});
}
);

View File

@ -11,5 +11,6 @@
"directives/MCTContainer",
"directives/MCTDrag",
"directives/MCTResize",
"directives/MCTScroll",
"StyleSheetLoader"
]