Low-level helper plugin API for data binding
Utility wrapper for tracking Polymer element properties updates.
Usage example:
``` js
Gerrit.install(plugin => {
plugin.getDomHook('change-view').onAttached(element => {
if (!element.content) { return; }
plugin.attributeHelper(element.content)
.get('change')
.then(change => {
// Is executed once on switching to change view.
});
});
plugin.getDomHook('reply-text').onAttached(element => {
if (!element.content) { return; }
plugin.attributeHelper(element.content)
.bind('text', replyText => {
// Is called every time reply text changes.
});
});
});
```
Change-Id: Ia95364df58489f71ea1fd591a160b73ac1d60e96
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2017 The Android Open Source Project
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
|
<dom-module id="gr-attribute-helper">
|
||||||
|
<script src="gr-attribute-helper.js"></script>
|
||||||
|
</dom-module>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (C) 2017 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
(function(window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function GrAttributeHelper(element) {
|
||||||
|
this.element = element;
|
||||||
|
this._promises = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
GrAttributeHelper.prototype._getChangedEventName = function(name) {
|
||||||
|
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + '-changed';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the property is defined on wrapped element.
|
||||||
|
* @param {string} name
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
GrAttributeHelper.prototype._elementHasProperty = function(name) {
|
||||||
|
return this.element[name] !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
GrAttributeHelper.prototype._reportValue = function(callback, value) {
|
||||||
|
try {
|
||||||
|
callback(value);
|
||||||
|
} catch (e) {
|
||||||
|
console.info(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds callback to property updates.
|
||||||
|
*
|
||||||
|
* @param {string} name Property name.
|
||||||
|
* @param {function(?)} callback
|
||||||
|
* @return {function()} Unbind function.
|
||||||
|
*/
|
||||||
|
GrAttributeHelper.prototype.bind = function(name, callback) {
|
||||||
|
const attributeChangedEventName = this._getChangedEventName(name);
|
||||||
|
const changedHandler = e => this._reportValue(callback, e.detail.value);
|
||||||
|
const unbind = () => this.element.removeEventListener(
|
||||||
|
attributeChangedEventName, changedHandler);
|
||||||
|
this.element.addEventListener(
|
||||||
|
attributeChangedEventName, changedHandler);
|
||||||
|
if (this._elementHasProperty(name)) {
|
||||||
|
this._reportValue(callback, this.element[name]);
|
||||||
|
}
|
||||||
|
return unbind;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value of the property from wrapped object. Waits for the property
|
||||||
|
* to be initialized if it isn't defined.
|
||||||
|
*
|
||||||
|
* @param {string} name Property name.
|
||||||
|
* @return {!Promise<?>}
|
||||||
|
*/
|
||||||
|
GrAttributeHelper.prototype.get = function(name) {
|
||||||
|
if (this._elementHasProperty(name)) {
|
||||||
|
return Promise.resolve(this.element[name]);
|
||||||
|
}
|
||||||
|
if (!this._promises[name]) {
|
||||||
|
let resolve;
|
||||||
|
const promise = new Promise(r => resolve = r);
|
||||||
|
const unbind = this.bind(name, value => {
|
||||||
|
resolve(value);
|
||||||
|
unbind();
|
||||||
|
});
|
||||||
|
this._promises[name] = promise;
|
||||||
|
}
|
||||||
|
return this._promises[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
window.GrAttributeHelper = GrAttributeHelper;
|
||||||
|
})(window);
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2017 The Android Open Source Project
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<title>gr-attribute-helper</title>
|
||||||
|
|
||||||
|
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||||
|
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||||
|
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||||
|
<link rel="import" href="gr-attribute-helper.html"/>
|
||||||
|
|
||||||
|
<script>void(0);</script>
|
||||||
|
|
||||||
|
<dom-element id="some-element">
|
||||||
|
<script>
|
||||||
|
Polymer({
|
||||||
|
is: 'some-element',
|
||||||
|
properties: {
|
||||||
|
fooBar: {
|
||||||
|
type: Object,
|
||||||
|
notify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</dom-element>
|
||||||
|
|
||||||
|
<test-fixture id="basic">
|
||||||
|
<template>
|
||||||
|
<some-element></some-element>
|
||||||
|
</template>
|
||||||
|
</test-fixture>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
suite('gr-attribute-helper tests', () => {
|
||||||
|
let element;
|
||||||
|
let instance;
|
||||||
|
let sandbox;
|
||||||
|
|
||||||
|
setup(() => {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
element = fixture('basic');
|
||||||
|
instance = new GrAttributeHelper(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
teardown(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolved on value change from undefined', () => {
|
||||||
|
const promise = instance.get('fooBar').then(value => {
|
||||||
|
assert.equal(value, 'foo! bar!');
|
||||||
|
});
|
||||||
|
element.fooBar = 'foo! bar!';
|
||||||
|
return promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolves to current attribute value', () => {
|
||||||
|
element.fooBar = 'foo-foo-bar';
|
||||||
|
const promise = instance.get('fooBar').then(value => {
|
||||||
|
assert.equal(value, 'foo-foo-bar');
|
||||||
|
});
|
||||||
|
element.fooBar = 'no bar';
|
||||||
|
return promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bind', () => {
|
||||||
|
const stub = sandbox.stub();
|
||||||
|
element.fooBar = 'bar foo';
|
||||||
|
const unbind = instance.bind('fooBar', stub);
|
||||||
|
element.fooBar = 'partridge in a foo tree';
|
||||||
|
element.fooBar = 'five gold bars';
|
||||||
|
assert.equal(stub.callCount, 3);
|
||||||
|
assert.deepEqual(stub.args[0], ['bar foo']);
|
||||||
|
assert.deepEqual(stub.args[1], ['partridge in a foo tree']);
|
||||||
|
assert.deepEqual(stub.args[2], ['five gold bars']);
|
||||||
|
stub.reset();
|
||||||
|
unbind();
|
||||||
|
instance.fooBar = 'ladies dancing';
|
||||||
|
assert.isFalse(stub.called);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||||
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
|
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
|
||||||
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
|
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
|
||||||
|
<link rel="import" href="../../plugins/gr-attribute-helper/gr-attribute-helper.html">
|
||||||
<link rel="import" href="../../plugins/gr-dom-hooks/gr-dom-hooks.html">
|
<link rel="import" href="../../plugins/gr-dom-hooks/gr-dom-hooks.html">
|
||||||
<link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
|
<link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
|
||||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||||
|
|||||||
@@ -318,6 +318,10 @@ limitations under the License.
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('attributeHelper', () => {
|
||||||
|
assert.isOk(plugin.attributeHelper());
|
||||||
|
});
|
||||||
|
|
||||||
suite('test plugin with base url', () => {
|
suite('test plugin with base url', () => {
|
||||||
setup(() => {
|
setup(() => {
|
||||||
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
|
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
|
||||||
|
|||||||
@@ -158,6 +158,10 @@
|
|||||||
return new GrThemeApi(this);
|
return new GrThemeApi(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Plugin.prototype.attributeHelper = function(element) {
|
||||||
|
return new GrAttributeHelper(element);
|
||||||
|
};
|
||||||
|
|
||||||
Plugin.prototype.getDomHook = function(endpointName, opt_options) {
|
Plugin.prototype.getDomHook = function(endpointName, opt_options) {
|
||||||
const hook = this._domHooks.getDomHook(endpointName);
|
const hook = this._domHooks.getDomHook(endpointName);
|
||||||
const moduleName = hook.getModuleName();
|
const moduleName = hook.getModuleName();
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ limitations under the License.
|
|||||||
'diff/gr-selection-action-box/gr-selection-action-box_test.html',
|
'diff/gr-selection-action-box/gr-selection-action-box_test.html',
|
||||||
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
|
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
|
||||||
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
|
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
|
||||||
|
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
|
||||||
'plugins/gr-external-style/gr-external-style_test.html',
|
'plugins/gr-external-style/gr-external-style_test.html',
|
||||||
'plugins/gr-plugin-host/gr-plugin-host_test.html',
|
'plugins/gr-plugin-host/gr-plugin-host_test.html',
|
||||||
'settings/gr-account-info/gr-account-info_test.html',
|
'settings/gr-account-info/gr-account-info_test.html',
|
||||||
|
|||||||
Reference in New Issue
Block a user