Polymer 1.0 Global Variables

16,212

Solution 1

Polymer element <iron-meta> is also an option. For me this was the easiest solution.

Solution 2

I've extended Etherealones' solution to work as a Behavior, and to extend Polymers "set" and "notifyPath" methods to trigger the updates automatically. This is as close as i could get to a true databinding across components/elements:

globals-behavior.html:

<script>
var instances = [];
var dataGlobal = {};

var GlobalsBehaviour = {

  properties: {
    globals: {
      type: Object,
      notify: true,
      value: dataGlobal
    }
  },

  ready: function() {
    var _setOrig = this.set;
    var _notifyPathOrig = this.notifyPath;
    this.set = function() {
      _setOrig.apply(this, arguments);
      if (arguments[0].split(".")[0] === "globals") {
        this.invokeInstances(_notifyPathOrig, arguments);
      }
    };
    this.notifyPath = function(path, value) {
      _notifyPathOrig.apply(this, arguments);
      if (arguments[0].split(".")[0] === "globals") {
        this.invokeInstances(_notifyPathOrig, arguments);
      }
    };
  },

  invokeInstances: function(fn, args) {
    var i;
    for (i = 0; i < instances.length; i++) {
      instance = instances[i];
      if (instance !== this) {
        fn.apply(instance, args);
      }
    }
  },

  attached: function() {
    instances.push(this);
  },

  detached: function() {
    var i;
    i = instances.indexOf(this);
    if (i >= 0) {
      instances.splice(i, 1);
    }
  }
};

</script>

And in all polymer elements that should have access to the globals variable:

  <script>
    Polymer({
      is: 'globals-enabled-element',
      behaviors: [GlobalsBehaviour]
    });
  </script>

Examples:

  1. I have posted a full example as a Gist on Github
  2. Here's a snippet to see it in action:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Globals Behavior Example</title>
        <link rel="import" href="//rawgit.com/Polymer/polymer/master/polymer.html">
        <dom-module id="globals-enabled-element">
          <template>
            <input type="text" value="{{globals.my_variable::input}}">
          </template>
          <script>
            var instances = [];
            var dataGlobal = {};
            
            var GlobalsBehaviour = {
              
              properties: {
                globals: {
                  type: Object,
                  notify: true,
                  value: dataGlobal
                }
              },
              
              ready: function() {
                var _setOrig = this.set;
                var _notifyPathOrig = this.notifyPath;
                this.set = function() {
                  _setOrig.apply(this, arguments);
                  if (arguments[0].split(".")[0] === "globals") {
                    this.invokeInstances(_notifyPathOrig, arguments);
                  }
                };
                this.notifyPath = function(path, value) {
                  _notifyPathOrig.apply(this, arguments);
                  if (arguments[0].split(".")[0] === "globals") {
                    this.invokeInstances(_notifyPathOrig, arguments);
                  }
                };
              },
              
              invokeInstances: function(fn, args) {
                var i;
                for (i = 0; i < instances.length; i++) {
                  instance = instances[i];
                  if (instance !== this) {
                    fn.apply(instance, args);
                  }
                }
              },
              
              attached: function() {
                instances.push(this);
              },
              
              detached: function() {
                var i;
                i = instances.indexOf(this);
                if (i >= 0) {
                  instances.splice(i, 1);
                }
              }
            };
          </script>
          <script>
            Polymer({
              is: 'globals-enabled-element',
              behaviors: [GlobalsBehaviour]
            });
          </script>
        </dom-module>
      </head>
      <body>
        <template is="dom-bind">
          <p>This is our first polymer element:</p>
          <globals-enabled-element id="element1"></globals-enabled-element>
          <p>And this is another one:</p>
          <globals-enabled-element id="element2"></globals-enabled-element>
        </template>
      </body>
    </html>

Solution 3

I have tried to improve on Alexei Volkov's answer, but I wanted to define the global variables separately. Instead of the getters/setters I used the observer property and saved the key together with the instances.

The usage is:

<app-data  key="fName" data="{{firstName}}" ></app-data>

whereas the keyproperty defines the name of the global variable.

So for example you can use:

<!-- Output element -->
<dom-module id="output-element" >
  <template>
    <app-data key="fName" data="{{data1}}" ></app-data>
    <app-data key="lName" data="{{data2}}" ></app-data>
    <h4>Output-Element</h4>
    <div>First Name: <span>{{data1}}</span></div>
    <div>Last Name: <span>{{data2}}</span></div>
  </template>
</dom-module>

<script>Polymer({is:'output-element'});</script>

Definition of the <app-data>dom module:

<dom-module id="app-data"></dom-module>
<script>
(function () {
    var instances = [];
    var vars = Object.create(Polymer.Base);

    Polymer({
        is: 'app-data',
        properties: {
           data: {
                type: Object,
                value: '',
                notify: true,
                readonly: false,
                observer: '_data_changed'
            },
          key: String
        },
        created: function () {
          key = this.getAttribute('key');
          if (!key){
            console.log(this);
            throw('app-data element requires key');
          }

          instances.push({key:key, instance:this});
        },

        detached: function () {
            key = this.getAttribute('key');
            var i = instances.indexOf({key:key, instance:this});
            if (i >= 0) {
                instances.splice(i, 1);
            }
        },

      _data_changed: function (newvalue, oldvalue) {
        key = this.getAttribute('key');
        if (!key){
            throw('_data_changed: app-data element requires key');
            }
        vars.set(key, newvalue);

        // notify the instances with the correct key
        for (var i = 0; i < instances.length; i++) 
        {
          if(instances[i].key == key)
          {
            instances[i].instance.notifyPath('data', newvalue);
          }
        }
      }


    });
})();
</script>

Fully working demo is here: http://jsbin.com/femaceyusa/1/edit?html,output

Solution 4

Sjmiles, one of Polymer's creators just posted the following snippet to the Polymer slack room as an example of shared data:

<!doctype html>
<html>
<head>

  <meta charset="utf-8">

  <meta name="description" content="shared-data element and repeats">

  <base href="http://milestech.net/components/">

  <script href="webcomponentsjs/webcomponents-lite.min.js"></script>
  <link href="polymer/polymer.html" rel="import">

</head>
<body>

  <demo-test></demo-test>

  <script>

    (function() {
      var private_data = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
      Polymer({
        is: 'private-shared-data',
        properties: {
          data: {
            type: Object,
            notify: true,
            value: function() {
              return private_data;
            }
          }
        }
      });
    })();

    Polymer({
      is: 'xh-api-device',
      properties: {
        data: {
          type: Array,
          notify: true
        },
        _share: {
          value: document.createElement('private-shared-data')
        }
      },
      observers: [
        'dataChanged(data.*)'
      ],
      ready: function() {
        this.data = this._share.data;
        this.listen(this._share, 'data-changed', 'sharedDataChanged');
      },
      dataChanged: function(info) {
        this._share.fire('data-changed', info, {bubbles: false});
      },
      sharedDataChanged: function(e) {
        this.fire(e.type, e.detail);
      },
      add: function(name) {
        this.push('data', {name: name});
      }
    });

  </script>

  <dom-module id="demo-test">
    <template>

      <h2>One</h2>

      <xh-api-device id="devices" data="{{data}}"></xh-api-device>

      <template is="dom-repeat" items="{{data}}">
        <div>name: <span>{{item.name}}</span></div>
      </template>

      <h2>Two</h2>

      <xh-api-device data="{{data2}}"></xh-api-device>

      <template is="dom-repeat" items="{{data2}}">
        <div>name: <span>{{item.name}}</span></div>
      </template>

      <br>
      <br>

      <button on-click="populate">Populate</button>

    </template>
    <script>
      Polymer({
        populate: function() {
          this.$.devices.add((Math.random()*100).toFixed(2));
          // this works too
          //this.push('data', {name: (Math.random()*100).toFixed(2)});
        }
      });
    </script>
  </dom-module>

</body>
</html>

I've actually moved my app to using simple data binding, so I'm not sure of the validity of this approach, but maybe it would be useful to someone.

Solution 5

I have implemented a pattern like iron-signals uses for this purpose. So the basic principle is that you manually notify other instances when an update occurs.

Consider this:

<dom-module id="x-global">
<script>
(function() {
  var instances = [];

  var dataGlobal = {};

  Polymer({
    is: 'x-global',

    properties: {
      data: {
        type: Object,
        value: dataGlobal,
      },
    },

    attached: function() {
      instances.push(this);
    },

    detached: function() {
      var i = instances.indexOf(this);
      if (i >= 0) {
        instances.splice(i, 1);
      }
    },

    set_: function(path, value) {
      this.set(path, value);

      instances.forEach(function(instance) {
        if (instance !== this) { // if it is not this one
          instance.notifyPath(path, value);
        }
      }.bind(this));
    },

    notifyPath_: function(path, value) {
      instances.forEach(function(instance) {
        instance.notifyPath(path, value);
      });
    },

    fire_: function(name, d) {
      instances.forEach(function(instance) {
        instance.fire(name, d);
      });
    },
  });
})();
</script>
</dom-module>

You will simple call the version that have an underscore suffix like fire_ when you are firing an event. You can even create a Polymer Behavior of some sort with this pattern I guess.

Beware that preceding underscore properties are already used by Polymer so don't go ahead and convert these to _fire.

P.S.: I didn't look around to solve how to reflect the notification of this.push(array, value); as I don't need it. I don't know if it's possible this way. Should go find Polymer.Base.push.

Share:
16,212
Grokys
Author by

Grokys

Updated on June 02, 2022

Comments

  • Grokys
    Grokys almost 2 years

    In Polymer 0.5 the advice on globals was as outlined in this question/answer:

    Polymer global variables

    However in Polymer 1.0 this doesn't seem to work. Change notifications are not automatically generated on the underlying model, they are generated on the <dom-module> instead which means that change notifications will be generated on only one of the <app-globals>.

    What is the recommended way of implementing this pattern in Polymer 1.0?

  • zerodevx
    zerodevx almost 9 years
    Thanks for posting the example! At first I thought it might be an anti-pattern to use 0.5-like global vars (since no mention of this was made in 1.0 docs), so I made the rather painful change to pure data-binding in my app. Glad that there's a way to achieve global var functionality in 1.0 too.
  • Tobias Strebitzer
    Tobias Strebitzer almost 9 years
    Great approach! I've extended it to be used as a Behavior, will post here as an answer.
  • Etherealone
    Etherealone almost 9 years
    I think this looks a bit overly complicated. So I guess it also replicates the array-changed event fired by this.push.
  • Etherealone
    Etherealone almost 9 years
    This is nice. Maybe find a way for push, pop, shift, unshift, and splice too? Polymer.push prototype may help maybe. There is an internal _notifySplice apparently. No idea how stable is that interface though.
  • Etherealone
    Etherealone almost 9 years
    Also, I'd rather provide the global variable to element myself, maybe you should expect the variable name _globals and use console.warn in Behavior.ready if it does not exist. A single global global does not sound good.
  • Let Me Tink About It
    Let Me Tink About It almost 9 years
    Your link did not show up on the post. Please repost with the link.
  • Grokys
    Grokys over 8 years
    I agree - implementing it as a behavior suggests that it's ready to be used by different components, but as far as I can tell, that is not the case here.
  • ootwch
    ootwch over 8 years
    Not as it is currently built, as the Polymer observer does not monitor/propagate change events on array elements. Check stackoverflow.com/a/30676440/1878199 for some ideas on how to change this if you really need it. I tend to reduce global variables instead, by adapting the design.
  • jkhoffman
    jkhoffman about 7 years
    Using this solution, I ran into a race condition situation with lazy-loaded components. As posted, lazy-loaded components are not initialized with the value from the shared data. See my post for a patch.
  • limitlessloop
    limitlessloop about 7 years
    Could this be modified to be used so you can import global variables as a dependency for a given custom element?
  • limitlessloop
    limitlessloop about 7 years
    Made a bit more sense to me with regards to my basic need of sharing variables from one source to other elements. I'm guessing you could change data from an array to an object if you needed to?
  • limitlessloop
    limitlessloop about 7 years
    I couldn't get this to work with Polymer 2.0, any idea what you'd have to update for it to work?
  • Ben Davis
    Ben Davis over 6 years
    Out of curiosity, why are you using this.getAttribute('key') instead of just this.key?