使用Polymer在同一Web组件的实例之间进行通信的最佳方式?

时间:2017-02-02 19:49:23

标签: polymer polymer-1.0 web-component polymer-2.x

我试图在同一元素的实例之间同步我的一些Web组件属性,因此如果其中一个属性发生更改,则相应的属性会在具有相应绑定和事件的所有实例中更新。

注意:我想使用Polymer Data System Concepts进行实例之间的通信。

实施例

我-element.html

<dom-module id="my-element">
  <script>
    Polymer({
      is: 'my-element',

      properties: {
        myProp: {
          type: String,
          notify: true
      }
    });
  </script>
</dom-module>

我-另一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>

我-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:使用标准模式和良好做法会很好。

4 个答案:

答案 0 :(得分:4)

TL;博士

我创建了一个自定义行为,可以同步所有具有notify: true的元素属性。工作原型:JSBin

目前,这个原型不区分不同类型的元素,这意味着它只能同步同一个自定义元素的实例 - 但这可以不费吹灰之力地进行更改。

您还可以定制行为,以便仅同步所需的属性,而不仅仅同步notify: true。但是,如果您采用此路径,请注意您要同步的所有属性必须具有notify: true,因为该行为会侦听<property-name>-changed事件,该事件仅被触发如果财产有notify: true

详情

让我们从自定义SyncBehavior行为开始:

(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的所有实例的变量。这是必不可少的,所以不要改变它。

如您所见,该行为由六个函数组成,即:

  1. attached,它将当前实例添加到实例列表中,并为notify: true的所有属性注册侦听器。
  2. detached,它从实例列表中删除当前实例,并删除notify: true的所有属性的侦听器。
  3. _eventHandlerForPropertyType,返回其中一个函数4-6的名称,具体取决于属性类型。
  4. __syncArray,它同步实例之间的Array类型属性。请注意,我忽略了当前目标并实现了一个简单的锁定机制,以避免循环。该方法处理两种情况:分配新数组,并改变现有数组。
  5. __syncObject,它同步实例之间的Object类型属性。请注意,我忽略了当前目标并实现了一个简单的锁定机制,以避免循环。该方法处理两种情况:分配新对象,以及更改现有对象的属性。
  6. __syncPrimitive,它同步实例之间属性的原始值。请注意,我忽略当前目标以避免循环。
  7. 为了测试我的新行为,我创建了一个示例自定义元素:

    <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行为。

    要在工作原型中组合上述所有内容,您只需执行以下操作:

    <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绑定了一个输入,而其他人根本没有绑定。我创建了一个简单的表单,涵盖了我能想到的每个场景:

    • 更改原始值。
    • 将项目推送到数组。
    • 创建一个新数组(包含一个项目)。
    • 从数组中删除项目。
    • 设置对象属性。
    • 创建新对象。

    修改

    我已更新了我的帖子和原型,以解决以下问题:

    • 同步非原始值,即Array和Object。
    • 将属性名称从Dash案例正确转换为Camel案例(反之亦然)。

答案 1 :(得分:3)

我们已经创建了一个组件来同步不同实例之间的数据。我们的组成部分是:

<sync-data
  key="email"
  scope="user"
  value="{{email}}"></sync-data>

我们在这样的组件中使用它:

<sync-data
  key="email"
  scope="user"
  value="{{userEmail}}"></sync-data>

在另一个组件中这样:

  <?php 
$array1=Array
(
Array
    (
        'RESPONSE' => 2,
        'RESPONSE_TEXT' =>'' ,
        'DEVELOPER_TEXT' => '',
        'RESPONSE_DATE_TIME' => "14-FEB-2017 11:09",
        'RESPONSE_DATE' => "2017-02-14 11:09:52",
        'RESPONSE_BY_ID' => 84,
        'RESPONSE_ASSIGNED_USER_NAME' => "bb"
    ),

Array
    (
        'RESPONSE' => 2,
        'RESPONSE_TEXT' =>'' ,
        'DEVELOPER_TEXT' => '',
        'RESPONSE_DATE_TIME' => "14-FEB-2017 11:09",
        'RESPONSE_DATE' => "2017-02-14 11:09:52",
        'RESPONSE_BY_ID' => 5,
        'RESPONSE_ASSIGNED_USER_NAME' => "cc"
    ),

Array
    (
       'RESPONSE' => 2,
        'RESPONSE_TEXT' =>'' ,
        'DEVELOPER_TEXT' => '',
        'RESPONSE_DATE_TIME' => "14-FEB-2017 11:09",
        'RESPONSE_DATE' => "2017-02-14 11:09:52",
        'RESPONSE_BY_ID' => 6,
        'RESPONSE_ASSIGNED_USER_NAME' => "ee"
    )

);

$array2=Array
(
Array
    (
        'RESPONSE_USER_NAME' => "my name"
    )

  );

$final_array=[];
  foreach($array1 as $key =>$value){
      foreach($array2 as $key1=>$value2){

          $value=array_merge($value, $value2);

      }
      $final_array[]=$value;

  }
  echo "<pre>";
  print_r($final_array);
?>

通过这种方式,我们可以获得事件和绑定的聚合物的原生行为

答案 2 :(得分:1)

我对这类问题的个人意见是使用flux architecture

创建一个包装器元素,它将所有信息分发给子节点。所有变化都通过主要组件进行。

<app-wrapper>
<component-x attr="[[someParam]]" />
<component-x attr="[[someParam]]" />
<component-x attr="[[someParam]]" />
</app-wrapper> 

component-x正在app-wrapper上触发更改值事件,app-wrapper正在更新someValue,请注意它是单向绑定。

有一个component for this,它正在实施redux架构,但也可以编写自己的代码。它或多或少是observer pattern

答案 3 :(得分:-1)

请尝试使用my-app.html。我没有看到任何理由不在这里使用双向绑定。

<dom-module id="my-app">
  <template>
    <my-element my-prop="{{myProp}}"></my-element>
    <my-element my-prop="{{myProp}}"></my-element>
  </template>
  <script>
    Polymer({
      is: 'my-app',
      ready: function() {
        this.myProp = 'test';
      }
    });
  </script>
</dom-module>

虽然使用myProp对象而不是properties回调为ready提供默认值可能是更好的做法。例如:

    Polymer({
      is: 'my-app',
      properties: {
        myProp: {
          type: String,
          value: 'test'
      }
    });
相关问题