Skip to content

Commit

Permalink
✨ Implement standard action toggleClass(...) (#16530)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinkassimo authored and aghassemi committed Jul 13, 2018
1 parent b0889ea commit ec8e691
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 9 deletions.
2 changes: 1 addition & 1 deletion build-system/tasks/bundle-size.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const log = require('fancy-log');
const {getStdout} = require('../exec');

const runtimeFile = './dist/v0.js';
const maxSize = '78.36KB';
const maxSize = '78.65KB';

const {green, red, cyan, yellow} = colors;

Expand Down
24 changes: 17 additions & 7 deletions examples/standard-actions.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@
margin: 0;
width: 140px;
}
#normal-element {
#normal-element, #normal-element2 {
background: #f0f0f0;
padding: 10px;
}
.spacer {
height: 100vh;
}
.red-text {
color: red;
}
</style>
</head>
<body>
Expand Down Expand Up @@ -75,12 +78,15 @@ <h3>Normal element:</h3>
<button on="tap:normal-element.show">Show</button>
<button on="tap:normal-element.hide">Hide</button>
<button on="tap:normal-element.toggleVisibility">Toggle Visibility</button>
<button on="tap:normal-element2.scrollTo">ScrollTo</button>
<button on="tap:normal-element2.scrollTo('position' = 'bottom')">ScrollTo Bottom</button>
<button on="tap:normal-element2.scrollTo('position' = 'center')">ScrollTo Center</button>
<button on="tap:normal-element2.scrollTo('duration' = 5000)">ScrollTo Slowly</button>
<button on="tap:normal-element2.toggleClass('class' = 'red-text')">Toggle Class</button>
<button on="tap:normal-element2.toggleClass('class' = 'red-text', 'force' = true)">Toggle Class (force = true)</button>
<button on="tap:normal-element2.toggleClass('class' = 'red-text', 'force' = false)">Toggle Class (force = false)</button>
<button on="tap:normal-element3.scrollTo">ScrollTo</button>
<button on="tap:normal-element3.scrollTo('position' = 'bottom')">ScrollTo Bottom</button>
<button on="tap:normal-element3.scrollTo('position' = 'center')">ScrollTo Center</button>
<button on="tap:normal-element3.scrollTo('duration' = 5000)">ScrollTo Slowly</button>
<button on="tap:input-element.focus">Focus</button>
<button on="tap:normal-element2.scrollTo('position' = 'top'), input-element.focus">ScrollTo and Focus</button>
<button on="tap:normal-element3.scrollTo('position' = 'top'), input-element.focus">ScrollTo and Focus</button>
</div>

<amp-img src="https://ampbyexample.com/img/amp.jpg" layout="responsive" width="1080" height="610" id="img-on-viewport"></amp-img>
Expand All @@ -89,10 +95,14 @@ <h3>Normal element:</h3>
I was initially hidden.
</div>

<div id="normal-element2">
I toggles between black and red.
</div>

<div class="spacer">
</div>

<div id="normal-element2">
<div id="normal-element3">
You have to scroll to see me.
</div>

Expand Down
4 changes: 4 additions & 0 deletions spec/amp-actions-and-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ event.response</pre></td>
<td><code>toggleVisibility</code></td>
<td>Toggles the visibility of the target element.</td>
</tr>
<tr>
<td><code>toggleClass(class=STRING, force=BOOLEAN)</code></td>
<td>Toggles class of the target element. <code>force</code> is optional, and if defined, it ensures that class would only be added but not removed if set to <code>true</code>, and only removed but not added if set to <code>false</code>.</td>
</tr>
<tr>
<td><code>scrollTo(duration=INTEGER, position=STRING)</code></td>
<td>Scrolls an element into view with a smooth animation. If defined,
Expand Down
31 changes: 31 additions & 0 deletions src/service/standard-actions-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export class StandardActions {
'scrollTo', this.handleScrollTo.bind(this));
actionService.addGlobalMethodHandler(
'focus', this.handleFocus.bind(this));
actionService.addGlobalMethodHandler(
'toggleClass', this.handleToggleClass.bind(this));
}

/**
Expand Down Expand Up @@ -267,6 +269,35 @@ export class StandardActions {
return this.handleHide(invocation);
}
}

/**
* Handles "toggleClass" action.
* @param {!./action-impl.ActionInvocation} invocation
* @return {?Promise}
*/
handleToggleClass(invocation) {
if (!invocation.satisfiesTrust(ActionTrust.HIGH)) {
return null;
}

const target = dev().assertElement(invocation.node);
const {args} = invocation;
const className = user().assertString(args['class'],
"Argument 'class' must be a string.");

this.resources_.mutateElement(target, () => {
if (args['force'] !== undefined) {
// must be boolean, won't do type conversion
const shouldForce = user().assertBoolean(args['force'],
"Optional argument 'force' must be a boolean.");
target.classList.toggle(className, shouldForce);
} else {
target.classList.toggle(className);
}
});

return null;
}
}


Expand Down
101 changes: 100 additions & 1 deletion test/functional/test-standard-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ describes.sandboxed('StandardActions', {}, () => {
expect(element.hasAttribute('hidden')).to.be.false;
}

function expectElementToHaveClass(element, className) {
expect(mutateElementStub).to.be.calledOnce;
expect(mutateElementStub.firstCall.args[0]).to.equal(element);
expect(element.classList.contains(className)).to.true;
}

function expectElementToDropClass(element, className) {
expect(mutateElementStub).to.be.calledOnce;
expect(mutateElementStub.firstCall.args[0]).to.equal(element);
expect(element.classList.contains(className)).to.false;
}

function expectAmpElementToHaveBeenHidden(element) {
expect(mutateElementStub).to.be.calledOnce;
expect(mutateElementStub.firstCall.args[0]).to.equal(element);
Expand Down Expand Up @@ -170,6 +182,89 @@ describes.sandboxed('StandardActions', {}, () => {
});
});

describe('"toggleClass" action', () => {
const dummyClass = 'i-amphtml-test-class-toggle';

it('should add class when not in classList', () => {
const element = createElement();
const invocation = {
node: element,
satisfiesTrust: () => true,
args: {
'class': dummyClass,
}};
standardActions.handleToggleClass(invocation);
expectElementToHaveClass(element, dummyClass);
});

it('should delete class when in classList', () => {
const element = createElement();
element.classList.add(dummyClass);
const invocation = {
node: element,
satisfiesTrust: () => true,
args: {
'class': dummyClass,
}};
standardActions.handleToggleClass(invocation);
expectElementToDropClass(element, dummyClass);
});

it('should add class when not in classList, when force=true', () => {
const element = createElement();
const invocation = {
node: element,
satisfiesTrust: () => true,
args: {
'class': dummyClass,
'force': true,
}};
standardActions.handleToggleClass(invocation);
expectElementToHaveClass(element, dummyClass);
});

it('should keep class when in classList, when force=true', () => {
const element = createElement();
element.classList.add(dummyClass);
const invocation = {
node: element,
satisfiesTrust: () => true,
args: {
'class': dummyClass,
'force': true,
}};
standardActions.handleToggleClass(invocation);
expectElementToHaveClass(element, dummyClass);
});

it('should not add when not in classList, when force=false', () => {
const element = createElement();
const invocation = {
node: element,
satisfiesTrust: () => true,
args: {
'class': dummyClass,
'force': false,
}};
standardActions.handleToggleClass(invocation);
expectElementToDropClass(element, dummyClass);
});

it('should delete class when in classList, when force=false', () => {
const element = createElement();
element.classList.add(dummyClass);
const invocation = {
node: element,
satisfiesTrust: () => true,
args: {
'class': dummyClass,
'force': false,
}};
standardActions.handleToggleClass(invocation);
expectElementToDropClass(element, dummyClass);
});
});

describe('"scrollTo" action', () => {
it('should handle normal element', () => {
const element = createElement();
Expand Down Expand Up @@ -405,7 +500,7 @@ describes.sandboxed('StandardActions', {}, () => {
expect(stub).to.be.calledOnce;

// Global actions.
expect(embedActions.addGlobalMethodHandler).to.have.callCount(5);
expect(embedActions.addGlobalMethodHandler).to.have.callCount(6);
expect(embedActions.addGlobalMethodHandler.args[0][0]).to.equal('hide');
expect(
embedActions.addGlobalMethodHandler.args[0][1]).to.be.a('function');
Expand All @@ -424,6 +519,10 @@ describes.sandboxed('StandardActions', {}, () => {
.equal('focus');
expect(
embedActions.addGlobalMethodHandler.args[4][1]).to.be.a('function');
expect(embedActions.addGlobalMethodHandler.args[5][0]).to
.equal('toggleClass');
expect(
embedActions.addGlobalMethodHandler.args[5][1]).to.be.a('function');
embedActions.addGlobalMethodHandler.args[0][1]();
expect(hideStub).to.be.calledOnce;
});
Expand Down

0 comments on commit ec8e691

Please sign in to comment.