问题描述
我正在尝试在同一元素的实例之间同步某些Web组件属性,因此,如果其中一个属性发生更改,则该属性将在所有实例中使用相应的绑定和事件进行更新.
I'm trying to sync some of my web component properties between instances of the same element so if one of this properties changes then the same property gets updated in all the instances with the corresponding binding and events.
注意:我想使用聚合物数据系统概念,用于实例之间的通信.
Note: I want to use the Polymer Data System Concepts for the communications between instances.
my-element.html
<dom-module id="my-element">
<script>
Polymer({
is: 'my-element',
properties: {
myProp: {
type: String,
notify: true
}
});
</script>
</dom-module>
my-other-element.html
<dom-module id="my-other-element">
<template>
<my-element my-prop="{{otherProp}}"></my-element>
</template>
<script>
Polymer({
is: 'my-other-element',
properties: {
otherProp: {
type: String,
notify: true,
readOnly: true
}
}
})
</script>
</dom-module>
my-app.html
<dom-module id="my-app">
<template>
<my-element id="element"></my-element>
<my-other-element id="otherElement"
on-other-prop-changed="onPropChanged"
></my-other-element>
</template>
<script>
Polymer({
is: 'my-app',
attached: function () {
// should set 'myProp' to 'test' and trigger
// the event 'my-prop-changed' in all my-element instances
this.$.element.myProp = 'test'
},
onPropChanged: function (ev, detail) {
console.log(detail.value); // should print 'test'
console.log(this.$.element.myProp); // should print 'test'
console.log(this.$.otherElement.otherProp); // should print 'test'
}
});
</script>
</dom-module>
PD:使用模式和良好做法等标准会很好.
PD: Would be good to use standard like patterns and good practices.
推荐答案
tl; dr
我创建了一个自定义行为,该行为将同步具有notify: true
的所有元素的属性.工作原型: JSBin .
tl;dr
I have created a custom behaviour that syncs all elements' properties that have notify: true
. Working prototype: JSBin.
当前,此原型无法区分不同类型的元素,这意味着它只能同步同一自定义元素的实例-但这可以轻松进行更改.
Currently, this prototype does not distinguish between different kinds of elements, meaning that it can only sync instances of the same custom element - but this can be changed without much effort.
您还可以调整行为,以便仅同步所需的属性,而不仅仅是与notify: true
同步.但是,如果采用此路径,建议您要同步的所有属性 必须具有notify: true
,因为该行为会监听<property-name>-changed
事件,该事件仅在该属性具有notify: true
.
You could also tailor the behaviour so that is syncs only the desired properties and not just all with notify: true
. However, if you take this path, be advised that all the properties you want to sync must have notify: true
, since the behaviour listens to the <property-name>-changed
event, which is fired only if the property has notify: true
.
让我们从自定义SyncBehavior
行为开始:
Let's start with the custom SyncBehavior
behaviour:
(function() {
var SyncBehaviorInstances = [];
var SyncBehaviorLock = false;
SyncBehavior = {
attached: function() {
// Add instance
SyncBehaviorInstances.push(this);
// Add listeners
for(var property in this.properties) {
if('notify' in this.properties[property] && this.properties[property].notify) {
// Watch all properties with notify = true
var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
this.listen(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
}
}
},
detached: function() {
// Remove instance
var index = SyncBehaviorInstances.indexOf(this);
if(index >= 0) {
SyncBehaviorInstances.splice(index, 1);
}
// Remove listeners
for(var property in this.properties) {
if('notify' in this.properties[property] && this.properties[property].notify) {
// Watch all properties with notify = true
var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
this.unlisten(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
}
}
},
_eventHandlerForPropertyType: function(propertyType) {
switch(propertyType) {
case 'Array':
return '__syncArray';
case 'Object':
return '__syncObject';
default:
return '__syncPrimitive';
}
},
__syncArray: function(event, details) {
if(SyncBehaviorLock) {
return; // Prevent cycles
}
SyncBehaviorLock = true; // Lock
var target = event.target;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
if(details.path === undefined) {
// New array -> assign by reference
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, details.value);
}
});
} else if(details.path.endsWith('.splices')) {
// Array mutation -> apply notifySplices
var splices = details.value.indexSplices;
// for all other instances: assign reference if not the same, otherwise call 'notifySplices'
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
var instanceReference = instance.get(prop);
var targetReference = target.get(prop);
if(instanceReference !== targetReference) {
instance.set(prop, targetReference);
} else {
instance.notifySplices(prop, splices);
}
}
});
}
SyncBehaviorLock = false; // Unlock
},
__syncObject: function(event, details) {
var target = event.target;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
if(details.path === undefined) {
// New object -> assign by reference
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, details.value);
}
});
} else {
// Property change -> assign by reference if not the same, otherwise call 'notifyPath'
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
var instanceReference = instance.get(prop);
var targetReference = target.get(prop);
if(instanceReference !== targetReference) {
instance.set(prop, targetReference);
} else {
instance.notifyPath(details.path, details.value);
}
}
});
}
},
__syncPrimitive: function(event, details) {
var target = event.target;
var value = details.value;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, value);
}
});
},
};
})();
请注意,我已经使用IIFE模式隐藏了保存自定义元素my-element
的所有实例的变量.这是必不可少的,因此请不要更改.
Notice that I have used the IIFE pattern to hide the variable that holds all instances of the custom element my-element
. This is essential, so don't change it.
如您所见,该行为包含六个功能,即:
As you can see, the behaviour consists of six functions, namely:
-
attached
,它将当前实例添加到实例列表,并使用notify: true
注册所有属性的侦听器. -
detached
,它从实例列表中删除当前实例,并使用notify: true
删除所有属性的侦听器. -
_eventHandlerForPropertyType
,根据属性类型返回函数4-6之一的名称. -
__syncArray
,它在实例之间同步Array类型的属性.请注意,我忽略了当前目标,并实现了一种简单的锁定机制以避免循环.该方法处理两种情况:分配新数组和对现有数组进行突变. -
__syncObject
,它在实例之间同步对象类型属性.请注意,我忽略了当前目标,并实现了一种简单的锁定机制以避免循环.该方法处理两种情况:分配新对象,以及更改现有对象的属性. -
__syncPrimitive
,它在实例之间同步属性的原始值.请注意,为了避免循环,我忽略了当前目标.
attached
, which adds the current instance to the list of instances and registers listeners for all properties withnotify: true
.detached
, which removes the current instance from the list of instances and removes listeners for all properties withnotify: true
._eventHandlerForPropertyType
, which returns the name of one of the functions 4-6, depending on the property type.__syncArray
, which syncs the Array type properties between the instances. Notice that I ignore the current target and implement a simple locking mechanism in order to avoid cycles. The method handles two scenarios: assigning a new Array, and mutating an existing Array.__syncObject
, which syncs the Object type properties between the instances. Notice that I ignore the current target and implement a simple locking mechanism in order to avoid cycles. The method handles two scenarios: assigning a new Object, and changing a property of an existing Object.__syncPrimitive
, which syncs the primitive values of properties between the instances. Notice that I ignore the current target in order to avoid cycles.
为了测试我的新行为,我创建了一个示例自定义元素:
In order to test-drive my new behaviour, I have created a sample custom element:
<dom-module id="my-element">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[id]]</h2>
<ul>
<li>propString: [[propString]]</li>
<li>
propArray:
<ol>
<template is="dom-repeat" items="[[propArray]]">
<li>[[item]]</li>
</template>
</ol>
</li>
<li>
propObject:
<ul>
<li>name: [[propObject.name]]</li>
<li>surname: [[propObject.surname]]</li>
</ul>
</li>
</ul>
</template>
<script>
Polymer({
is: 'my-element',
behaviors: [
SyncBehavior,
],
properties: {
id: {
type: String,
},
propString: {
type: String,
notify: true,
value: 'default value',
},
propArray: {
type: Array,
notify: true,
value: function() {
return ['a', 'b', 'c'];
},
},
propObject: {
type: Object,
notify: true,
value: function() {
return {'name': 'John', 'surname': 'Doe'};
},
},
},
pushToArray: function(item) {
this.push('propArray', item);
},
pushToNewArray: function(item) {
this.set('propArray', [item]);
},
popFromArray: function() {
this.pop('propArray');
},
setObjectName: function(name) {
this.set('propObject.name', name);
},
setNewObjectName: function(name) {
this.set('propObject', {'name': name, 'surname': 'unknown'});
},
});
</script>
</dom-module>
它具有一个String属性,一个Array属性和一个Object属性;全部带有notify: true
.自定义元素还实现了SyncBehavior
行为.
It has one String property, one Array property, and one Object property; all with notify: true
. The custom element also implements the SyncBehavior
behaviour.
要将以上所有内容结合到一个可行的原型中,只需执行以下操作:
To combine all of the above in a working prototype, you simply do this:
<template is="dom-bind">
<h4>Primitive type</h4>
propString: <input type="text" value="{{propString::input}}" />
<h4>Array type</h4>
Push to propArray: <input type="text" id="propArrayItem" /> <button onclick="_propArrayItem()">Push</button> <button onclick="_propNewArrayItem()">Push to NEW array</button> <button onclick="_propPopArrayItem()">Delete last element</button>
<h4>Object type</h4>
Set 'name' of propObject: <input type="text" id="propObjectName" /> <button onclick="_propObjectName()">Set</button> <button onclick="_propNewObjectName()">Set to NEW object</button> <br />
<script>
function _propArrayItem() {
one.pushToArray(propArrayItem.value);
}
function _propNewArrayItem() {
one.pushToNewArray(propArrayItem.value);
}
function _propPopArrayItem() {
one.popFromArray();
}
function _propObjectName() {
one.setObjectName(propObjectName.value);
}
function _propNewObjectName() {
one.setNewObjectName(propObjectName.value);
}
</script>
<my-element id="one" prop-string="{{propString}}"></my-element>
<my-element id="two"></my-element>
<my-element id="three"></my-element>
<my-element id="four"></my-element>
</template>
在此原型中,我创建了四个my-element
实例.一个绑定了propString
到一个输入,而其他的根本没有任何绑定.我创建了一个简单的表格,涵盖了我可能想到的每种情况:
In this prototype, I have created four instances of my-element
. One has propString
bound to an input, while the others don't have any bindings at all. I have created a simple form, that covers every scenario I could think of:
- 更改原始值.
- 将项目推到数组中.
- 创建一个新数组(包含一个项目).
- 从数组中删除一项.
- 设置对象属性.
- 创建一个新对象.
我已经更新了我的帖子和原型,以解决以下问题:
I have updated my post and the prototype in order to address the following issues:
- 同步非原始值,即Array和Object.
- 正确地将属性名称从Dash大小写转换为Camel大小写(反之亦然).
这篇关于使用Polymer在同一Web组件的实例之间进行通信的最佳方式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!