Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing a DOM attribute doesn't change the model #246

Closed
abarth opened this issue Aug 21, 2013 · 6 comments
Closed

Changing a DOM attribute doesn't change the model #246

abarth opened this issue Aug 21, 2013 · 6 comments

Comments

@abarth
Copy link

abarth commented Aug 21, 2013

I'm not sure whether this behavior is expected, but it surprised me. I would expect that setting a DOM attribute would mutate the model value wired up to that attribute, like it does for custom elements.

For the test case below, I see "PASS (2 of 2)" displayed but not "PASS (1 of 2)". That indicates that changing the custom element's attribute mutated the model but that changing a regular element's attribute did not mutate the model.

<!DOCTYPE html>
<script src="../third_party/polymer/polymer.js"></script>
<polymer-element name="test-custom" attributes="value">
  <template>
  </template>
  <script>
  Polymer('test-custom', {
    value: null,
  });
  </script>
</polymer-element>
<polymer-element name="test-flag" attributes="go">
  <template>
    <div id="foo" value="{{ value1 }}"></div>
    <div>{{ value1 }}</div>
    <test-custom id="bar" value="{{ value2 }}"></test-custom>
    <div>{{ value2 }}</div>
  </template>
  <script>
  Polymer('test-flag', {
    value1: null,
    value2: null,
    goChanged: function() {
      this.$.foo.setAttribute('value', 'PASS (1 of 2)');
      this.$.bar.setAttribute('value', 'PASS (2 of 2)');
    },
  });
  </script>
</polymer-element>
<test-flag go="true"></test-flag>
@sjmiles
Copy link
Contributor

sjmiles commented Aug 21, 2013

We multiplexed the {{ }} syntax to support two different forms of binding.

<div id="foo" value="{{ value1 }}"></div>

Is a DOM binding: it's a one-way binding of value1 to the value attribute of #foo.

IFF test-custom is a polymer-element that has published the value attribute, then the following:

<test-custom id="bar" value="{{ value2 }}"></test-custom>

is interpreted as a direct property binding between bar.value and the host's value2. Property bindings are two-way (actually, n-way). IOW, after setting up this binding, there is a single source of truth for the data of value and value2. Setting either value, or reading either value, will always produce the same data.

This multiplexing is designed to reduce cognitive load on users: there is one syntax for binding. Whether that's the right trade-off is open for discussion.

@abarth
Copy link
Author

abarth commented Aug 21, 2013

How does <input value="{{ value3 }}"> work? When I type in the input box, the text I type seems to be stored in value3 but if I set the value attribute via the DOM, the value doesn't appear to make its way into value3.

@sjmiles
Copy link
Contributor

sjmiles commented Aug 21, 2013

Yes, my "IFF" was not correct.

Short answer, <input> and a few other native elements also support two-way binding.

Long answer: individual nodes get to decide what to do with mustache syntax via the Node.prototype.bind interface. HTMLElement.prototype.bind is implemented to do the one-way binding I described above.

HTMLInputElement.prototype.bind has special code to do two-way binding to value. IIRC, <select> does too.

All polymer-elements override the bind() method and do two-way binding on attributes that are published, as I said.

Ultimately, any element could have a bind() override to do whatever special processing, depending on the run-time setup.

@abarth
Copy link
Author

abarth commented Aug 21, 2013

Makes sense. Thank you for the explanation! I'm going to close this bug, but it might help future developers understand if this information was captured in the documentation somewhere.

@abarth abarth closed this as completed Aug 21, 2013
@ojanvafai
Copy link

I'm still confused here...why doesn't the following case work as I expect it to?

<!DOCTYPE html>
<script src="third_party/polymer/polymer.js"></script>
<polymer-element name="rietveld-app">
  <template>
    <rietveld-issuelist selected="{{ number }}"></rietveld-issuelist>
    <rietveld-issue number="{{ number }}"></rietveld-issue>
  </template>
  <script>
  Polymer('rietveld-app', {
    ready: function() {
      this.number = 'initial';
    }
  });
  </script>
</polymer-element>
<polymer-element name="rietveld-issuelist" attributes="selected">
  <template>
    <div on-tap="selectIssue">tap me {{ selected }}</div>
  </template>
  <script>
  Polymer('rietveld-issuelist', {
    selectIssue: function(event) {
      this.selected = "changed";
    }
  });
  </script>
</polymer-element>
<polymer-element name="rietveld-issue" attributes="number">
  <template>This should change to "changed" after the tap: {{ number }}</template>
  <script>
  Polymer('rietveld-issue', {});
  </script>
</polymer-element>
<rietveld-app></rietveld-app>

@sorvell
Copy link
Contributor

sorvell commented Aug 26, 2013

It's a dependency ordering problem. rietveld-app depends on
rietveld-issuelist
and rietveld-issuelist so they must be available when the <rietveld-app>
element is created. If you put the polymer-elements for rietveld-issuelist
and rietveld-issuelist above the one for rietveld-app everything should
work.

Even though the dependency ordering is not correct here, there's a more
subtle reason why this fails. It would be reasonable to assume that all
dependencies are ready by the time the first <rietveld-app> is parsed.
However, this is not the case due to the way polymer polyfills HTMLImports.
The parser is allowed to pass over the document and when all imports are
ready, elements are upgraded. Therefore, <polymer-element name= "rietveld-app"> is registered and immediately upgrades the <rietveld-app>
instance at the bottom of the page, before the other dependencies are
available.

When <rietveld-app> upgrades, it creates bindings to the elements in its
shadowRoot. These elements are not upgraded at that time. Because of this,
polymer does not know to treat these bindings as property rather than
attribute bindings. When the other dependencies are registered, they do
upgrade in the rietveld-app instance, but the bindings are already
incorrect.

This is good candidate for a warning message and I've posted this issue to
help us track it: #258

On Sat, Aug 24, 2013 at 1:43 PM, ojanvafai notifications@github.com wrote:

I'm still confused here...why doesn't the following case work as I expect
it to?

<script src="third_party/polymer/polymer.js"></script> <script> Polymer('rietveld-app', { ready: function() { this.number = 'initial'; } }); </script>
tap me {{ selected }}
<script> Polymer('rietveld-issuelist', { selectIssue: function(event) { this.selected = "changed"; } }); </script> This should change to "changed" after the tap: {{ number }} <script> Polymer('rietveld-issue', {}); </script>


Reply to this email directly or view it on GitHubhttps://github.com//issues/246#issuecomment-23216210
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants