I have a search input field to receive user input and a variable-state button that responds to user input. Notice the on-input
event on the search input.
<dom-module id="app-search">
<template>
<input type="search" id="searchInput" on-input="_onInput" />
<!-- NOTE: button is set to 'disabled' when there is no value of search field -->
<paper-button disabled>Done</paper-button>
</template>
</dom-module>
In the Polymer ready()
definition I get a handle to the paper-button
element (The ready()
function is called after everything, including after the element and its properties have been initialized, so it's a good time to query the local DOM).
ready() {
super.ready(); // must call this for Polymer to work
// get handle to paper-button element
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
}
(By the way, I know using this.$
can be used as a syntax short-cut for Polymer.dom(this.root).querySelector()
but that syntax only seems to work for targeting elements in the local DOM that have an id
, e.g: this.$.searchInput
will return a handle to the element with id="searchInput"
. Does anyone know of a shorthand for targeting non-id, regular elements without having to type Polymer.dom(this.root)...
?)
I have a function that detects input
events on the search field. If there is a value in the search field, enable the button.
_onInput() {
// if search input has value
if (Boolean(this.$.searchInput.value)) {
// remove disabled attr from button
this._doneBtn.removeAttribute('disabled');
} else {
this._disableBtn();
}
}
_disableBtn() {
this._doneBtn.setAttribute('disabled', true);
}
Up to now, this works so that when a user starts typing, the button becomes enabled; when there is no value the button becomes disabled.
However, by convention, users can also delete the input value by clicking the little 'x' that appears on the right-hand-side of the search input. Developers can detect that event by attaching a search
event to the input element:
ready() {
super.ready(); // must call this for Polymer to work
// get handle to paper-button element
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
// attach 'search' event listener to search input field
// call the element's '_disableBtn' function
this.$.searchInput.addEventListener('search', this._disableBtn);
}
The problem is when I trigger the event by clicking the 'x' that appears when the search field has a value, the this._disableBtn
function fires, but this._doneBtn
inside the function returns undefined:
Uncaught TypeError: Cannot read property 'setAttribute' of undefined.
Assuming this might have to do with an improper type definition, I tried declaring the _doneBtn
property in the Polymer properties getter:
static get properties() {
return {
_doneBtn: Object // also tried 'String'
}
}
I also tried querying the DOM from inside the _disabledBtn
function again and trying to re-declare the property but I still get the same error:
_disableBtn() {
if (!this._doneBtn) {
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
}
this._doneBtn.setAttribute('disabled', true);
}
Can anyone understand what's happening here? This seems to have something to do with the event listener. Perhaps the DOM is not fully rendered before it's parsed although switching the order of the declarations in the ready()
call doesn't make a difference? It could also have something to do with this
.
Interestingly, when I console.log(this)
inside _disableBtn
, the console returns two different this
instances, one for the host element (<app-search>
) and one for the target element that fired the event: two elements point to 'this'. Also noteworthy is the order that this
is printed.
I'm hoping someone wiser than me can help solve what's going on here.
After reading @softjake's response, I was able to solve the problem.
First, let's revisit the addEventListener()
setup that was added in the ready()
function:
ready() {
super.ready();
// handle to toggle button element
this._doneBtn = Polymer.dom(this.root).querySelector('paper-button');
// previous method === no worky
//this.$.searchInput.addEventListener('search', this._disableBtn);
// changed it to:
this.$.searchInput.addEventListener('search', this._disableBtn.bind(this._doneBtn));
}
The key here is that I'm using .bind(this._doneBtn)
to make sure this
, inside the scope of the _disableBtn
function refers to this._doneBtn
and not some other this
(like the parent element or document/window).
Finally, adjust the _disableBtn
function slightly:
_disableBtn() {
// previous (also no worky)
//this._doneBtn.setAttribute('disabled', true);
// changed to:
this.setAttribute('disabled', true);
}
Because this
already refers to this._doneBtn
, we can simply use this.setAttribute(...)
.