Support loading for button and allow event listener to set it
currently gr-button supports `loading` but did not use it, this change will:
1. use disabled status when loading
2. add a spinner to show when loading is true
3. in repo-access component, set loading to true while promise ongoing
4. also fix the issue with non-zero initial tabindex for gr-button
screenshot: https://imgur.com/a/NCLwb45
Bug: 12421
Change-Id: Ie69c447455d23a118dbe1823c576144e0bca7c2d
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
index fab730d..54006b5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
@@ -105,12 +105,12 @@
primary
class$="[[_computeSaveBtnClass(_ownerOf)]]"
on-click="_handleSave"
- disabled$="[[!_modified]]">Save</gr-button>
+ disabled="[[!_modified]]">Save</gr-button>
<gr-button id="saveReviewBtn"
primary
class$="[[_computeSaveReviewBtnClass(_canUpload)]]"
on-click="_handleSaveForReview"
- disabled$="[[!_modified]]">Save for review</gr-button>
+ disabled="[[!_modified]]">Save for review</gr-button>
<template
is="dom-repeat"
items="{{_sections}}"
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index b350702..02b62e0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -442,21 +442,42 @@
return obj;
}
- _handleSave() {
+ _handleSave(e) {
const obj = this._getObjforSave();
if (!obj) { return; }
+ const button = e && e.target;
+ if (button) {
+ button.loading = true;
+ }
return this.$.restAPI.setRepoAccessRights(this.repo, obj)
.then(() => {
this._reload(this.repo);
+ })
+ .finally(() => {
+ this._modified = false;
+ if (button) {
+ button.loading = false;
+ }
});
}
- _handleSaveForReview() {
+ _handleSaveForReview(e) {
const obj = this._getObjforSave();
if (!obj) { return; }
- return this.$.restAPI.setRepoAccessRightsForReview(this.repo, obj)
+ const button = e && e.target;
+ if (button) {
+ button.loading = true;
+ }
+ return this.$.restAPI
+ .setRepoAccessRightsForReview(this.repo, obj)
.then(change => {
Gerrit.Nav.navigateToChange(change);
+ })
+ .finally(() => {
+ this._modified = false;
+ if (button) {
+ button.loading = false;
+ }
});
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 2fc31f7..d89b5df 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -1138,6 +1138,53 @@
assert.equal(Object.keys(element._local).length, 1);
});
+ test('_handleSave', done => {
+ const repoAccessInput = {
+ add: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: {action: 'DENY', modified: true},
+ },
+ },
+ },
+ },
+ },
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: {},
+ },
+ },
+ },
+ },
+ },
+ };
+ sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns(
+ Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
+ sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ let resolver;
+ const saveStub = sandbox.stub(element.$.restAPI,
+ 'setRepoAccessRights')
+ .returns(new Promise(r => resolver = r));
+
+ element.repo = 'test-repo';
+ sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
+
+ element._modified = true;
+ MockInteractions.tap(element.$.saveBtn);
+ assert.equal(element.$.saveBtn.hasAttribute('loading'), true);
+ resolver({_number: 1});
+ flush(() => {
+ assert.isTrue(saveStub.called);
+ assert.isTrue(Gerrit.Nav.navigateToChange.notCalled);
+ done();
+ });
+ });
+
test('_handleSaveForReview', done => {
const repoAccessInput = {
add: {
@@ -1166,14 +1213,19 @@
sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns(
Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ let resolver;
const saveForReviewStub = sandbox.stub(element.$.restAPI,
'setRepoAccessRightsForReview')
- .returns(Promise.resolve({_number: 1}));
+ .returns(new Promise(r => resolver = r));
element.repo = 'test-repo';
sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
- element._handleSaveForReview().then(() => {
+ element._modified = true;
+ MockInteractions.tap(element.$.saveReviewBtn);
+ assert.equal(element.$.saveReviewBtn.hasAttribute('loading'), true);
+ resolver({_number: 1});
+ flush(() => {
assert.isTrue(saveForReviewStub.called);
assert.isTrue(Gerrit.Nav.navigateToChange
.lastCall.calledWithExactly({_number: 1}));
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index 498bf6b..729f87d 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -137,7 +137,7 @@
/* Keep below color definition for primary so that this takes precedence
when disabled. */
- :host([disabled]) {
+ :host([disabled]), :host([loading]) {
--background-color: var(--disabled-button-background-color);
--text-color: var(--deemphasized-text-color);
cursor: default;
@@ -149,7 +149,8 @@
--margin: 0;
--padding: 5px 4px;
}
- :host([disabled][link]) {
+ :host([disabled][link]),
+ :host([loading][link]) {
--background-color: transparent;
--text-color: var(--deemphasized-text-color);
cursor: default;
@@ -173,8 +174,11 @@
</style>
<paper-button
raised="[[!link]]"
- disabled="[[_computeDisabled(disabled, loading)]]"
+ disabled="[[_disabled]]"
tabindex="-1">
+ <template is="dom-if" if="[[loading]]">
+ <span class="loadingSpin"></span>
+ </template>
<slot></slot>
<i class="downArrow"></i>
</paper-button>
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index a078be7..9d96038 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -42,11 +42,6 @@
value: false,
reflectToAttribute: true,
},
- loading: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
disabled: {
type: Boolean,
observer: '_disabledChanged',
@@ -56,24 +51,29 @@
type: Boolean,
value: false,
},
- _enabledTabindex: {
+ loading: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ },
+
+ _disabled: {
+ type: Boolean,
+ computed: '_computeDisabled(disabled, loading)',
+ },
+
+ _initialTabindex: {
type: String,
value: '0',
},
};
}
- static get observers() {
- return [
- '_computeDisabled(disabled, loading)',
- ];
- }
-
/** @override */
created() {
super.created();
- this.addEventListener('click',
- e => this._handleAction(e));
+ this._initialTabindex = this.getAttribute('tabindex') || '0';
+ this.addEventListener('click', e => this._handleAction(e));
this.addEventListener('keydown',
e => this._handleKeydown(e));
}
@@ -86,10 +86,13 @@
}
_handleAction(e) {
- if (this.disabled) {
+ if (this._disabled) {
e.preventDefault();
+ e.stopPropagation();
e.stopImmediatePropagation();
+ return;
}
+
let el = this.root;
let path = '';
while (el = el.parentNode || el.host) {
@@ -106,10 +109,7 @@
}
_disabledChanged(disabled) {
- if (disabled) {
- this._enabledTabindex = this.getAttribute('tabindex') || '0';
- }
- this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex);
+ this.setAttribute('tabindex', disabled ? '-1' : this._initialTabindex);
this.updateStyles();
}
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
index 0aec751..cfac37f 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
@@ -35,8 +35,14 @@
</template>
</test-fixture>
+<test-fixture id="tabindex">
+ <template>
+ <gr-button tabindex="3"></gr-button>
+ </template>
+</test-fixture>
+
<script>
- suite('gr-select tests', async () => {
+ suite('gr-button tests', async () => {
await readyToTest();
let element;
let sandbox;
@@ -60,18 +66,32 @@
sandbox.restore();
});
- test('disabled is set by disabled or loading', () => {
- assert.isFalse(element.shadowRoot
- .querySelector('paper-button').disabled);
+ test('disabled is set by disabled', () => {
+ const paperBtn = element.shadowRoot.querySelector('paper-button');
+ assert.isFalse(paperBtn.disabled);
element.disabled = true;
- assert.isTrue(element.shadowRoot
- .querySelector('paper-button').disabled);
+ assert.isTrue(paperBtn.disabled);
element.disabled = false;
- assert.isFalse(element.shadowRoot
- .querySelector('paper-button').disabled);
- element.loading = true;
- assert.isTrue(element.shadowRoot
- .querySelector('paper-button').disabled);
+ assert.isFalse(paperBtn.disabled);
+ });
+
+ test('loading set from listener', done => {
+ let resolve;
+ element.addEventListener('click', e => {
+ e.target.loading = true;
+ resolve = () => e.target.loading = false;
+ });
+ const paperBtn = element.shadowRoot.querySelector('paper-button');
+ assert.isFalse(paperBtn.disabled);
+ MockInteractions.tap(element);
+ assert.isTrue(paperBtn.disabled);
+ assert.isTrue(element.hasAttribute('loading'));
+ resolve();
+ flush(() => {
+ assert.isFalse(paperBtn.disabled);
+ assert.isFalse(element.hasAttribute('loading'));
+ done();
+ });
});
test('tabindex should be -1 if disabled', () => {
@@ -82,22 +102,42 @@
// Regression tests for Issue: 11969
test('tabindex should be reset to 0 if enabled', () => {
element.disabled = false;
- assert.isTrue(element.getAttribute('tabindex') === '0');
+ assert.equal(element.getAttribute('tabindex'), '0');
element.disabled = true;
- assert.isTrue(element.getAttribute('tabindex') === '-1');
+ assert.equal(element.getAttribute('tabindex'), '-1');
element.disabled = false;
- assert.isTrue(element.getAttribute('tabindex') === '0');
+ assert.equal(element.getAttribute('tabindex'), '0');
+ });
+
+ test('tabindex should be preserved', () => {
+ element = fixture('tabindex');
+ element.disabled = false;
+ assert.equal(element.getAttribute('tabindex'), '3');
+ element.disabled = true;
+ assert.equal(element.getAttribute('tabindex'), '-1');
+ element.disabled = false;
+ assert.equal(element.getAttribute('tabindex'), '3');
});
// 'tap' event is tested so we don't loose backward compatibility with older
// plugins who didn't move to on-click which is faster and well supported.
- for (const eventName of ['tap', 'click']) {
- test('dispatches ' + eventName + ' event', () => {
- const spy = addSpyOn(eventName);
- MockInteractions.tap(element);
- assert.isTrue(spy.calledOnce);
- });
- }
+ test('dispatches click event', () => {
+ const spy = addSpyOn('click');
+ MockInteractions.click(element);
+ assert.isTrue(spy.calledOnce);
+ });
+
+ test('dispatches tap event', () => {
+ const spy = addSpyOn('tap');
+ MockInteractions.tap(element);
+ assert.isTrue(spy.calledOnce);
+ });
+
+ test('dispatches click from tap event', () => {
+ const spy = addSpyOn('click');
+ MockInteractions.tap(element);
+ assert.isTrue(spy.calledOnce);
+ });
// Keycodes: 32 for Space, 13 for Enter.
for (const key of [32, 13]) {