Can't find element on iron ajax event methods - polymer

For below given code this.$.createButton or document.querySelector('createButton') works inside the ready method. But same code fails inside handleRequestSent or handleResponseReceived. The error message I get is
Uncaught TypeError: Cannot read property 'createButton' of undefined
I tried to debug, it appears that inside handleRequestSent and handleResponseReceived the this actually pointing to iron-ajax, but not the element dom root. Any suggestion is welcome. Thanks in advance.
<dom-module id="bortini-tv-create">
<template>
<form>
<paper-input label="Name" value="{{tv.name}}"></paper-input>
<paper-input label="Logo" value="{{tv.logo}}"></paper-input>
<paper-input label="Address" value="{{tv.address}}"></paper-input>
<paper-input label="Web site" value="{{tv.webSite}}"></paper-input>
<paper-input label="Registration number" value="{{tv.regNumber}}"></paper-input>
<br/>
<br/>
<paper-button id="createButton" raised on-tap="handleTvCreate">
<iron-icon icon="redeem"></iron-icon>
Add
</paper-button>
<paper-button id="cancelButton" raised on-tap="handleCancelTvCreate">
<iron-icon icon="cancel"></iron-icon>
Cancel
</paper-button>
</form>
<iron-ajax
id="ironAjax"
url="/api/tv"
content-type="application/json"
handle-as="json"
method="POST">
</iron-ajax>
<paper-toast id="toast"
duration="3000"
text="TV {{tv.name}} has been created">
</paper-toast>
</template>
</dom-module>
<script>
function initModel() {
return {"name": "", "logo": "", "address": "", "webSite": "", "regNumber": ""};
}
Polymer({
is: "bortini-tv-create",
properties: {
tv: {
type: Object,
value: initModel(),
notify: true
}
},
ready: function() {
this.$.ironAjax.addEventListener('error', this.handleError);
this.$.ironAjax.addEventListener('request', this.handleRequestSent);
this.$.ironAjax.addEventListener('response', this.handleResponseReceived);
},
handleTvCreate: function (event) {
var ironAjax=document.querySelector("#ironAjax");
ironAjax.body = JSON.stringify(this.tv);
ironAjax.generateRequest();
},
handleCancelTvCreate: function (event) {
MoreRouting.navigateTo('tv-list');
},
handleError: function(event) {
this.$.createButton.disabled=false;
this.$.cancelButton.disabled=false;
var request=event.detail.request;
var error=event.detail.error;
var toast=document.querySelector("#toast");
toast.text="Error: "+error;
toast.show();
},
handleRequestSent: function (request) {
console.log("boooooo");
this.$.createButton.disabled=true;
this.$.cancelButton.disabled=true;
},
handleResponseReceived: function (response) {
document.querySelector('createButton').disabled=false;
document.querySelector('cancelButton').disabled=false;
console.log("Received response: " + JSON.stringify(response.detail.response));
document.querySelector('#toast').show();
MoreRouting.navigateTo('tv-list');
}
});
</script>

Because you set your event handlers imperatively the scope (i.e this) the handler is called with, isn't your element instance.
Two ways to solve this:
Bind the event handler to your element instance:
this.$.ironAjax.addEventListener('error', this.handleError.bind(this));
This will make sure that this inside of the event handler is actually the this you want.
Define the event handlers declaratively, then Polymer will take care of the proper binding:
<iron-ajax ... on-error="handleError" ...></iron-ajax>

this is because the responseHandler method is fired from an
iron-request-element inside. So the this-context moves to the iron-ajax element. If you want to return to your "original" firing element from there, simply use the this.parentElement try something like this:
[...]
handleResponseReceived: function( request )
{
var that = this.parentElement;
that.$.createButton.disabled=true;
}
This should do the trick. Sorry for answering that late. ;)

Related

Two way data binding with Polymer.Templatizer

I am trying to get two way data-binding between a host element and a template in Polymer using templatizer. For example if I am trying to keep two input boxes in-sync:
<html>
<body>
<my-element>
<template >
<input type="text" value="{{test::change}}" />
<div>The value of 'test' is: <span>{{test}}</span></div>
</template>
</my-element>
<dom-module id="my-element">
<template>
<input type="text" value="{{test::change}}" />
value:
<p>{{test}}</p>
<div id="items"></div>
<content id="template"></content>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element',
test: {
type: String,
value: "a"
},
behaviors: [ Polymer.Templatizer ],
_forwardParentProp: function(prop, value) {debugger},
_forwardParentPath: function(path, value) {debugger},
_forwardInstanceProp: function(inst, prop, value) {debugger},
_forwardInstancePath: function(inst, path, value) {debugger},
ready: function() {
this._instanceProps = {
test: true
};
var templates = Polymer.dom(this.$.template).getDistributedNodes();
template = templates[1];
this.templatize(template);
var itemNode = this.stamp({ test: this.test});
Polymer.dom(this.$.items).appendChild(itemNode.root);
}
});
</script>
</body>
</html>
In the above code I hit the debugger in the _forwardInstanceProp but not any of the others. Why is this? Inside _forwardInstanceProp I can access my-element and manually update the test property. Is there a better way to do this? I also could add an observer on my-element to the test property and then propagate any changes in my-element to the template. Is there a better way to do that? I am just trying to understand what all four of these methods do and when/why they should be used.
It beats my why I can never get neither _forwardParentPath nor _forwardParentProp to run. However, I know when the other two run :)
_forwardInstanceProp runs for direct properties of model passed to stamp and _instanceProps is initialized:
this._instanceProps = {
text: true
};
var clone = this.stamp({
text: this.text
});
_forwardInstancePath on the other hand runs when you pass nested objects to stamp:
var clone = this.stamp({
nested: {
text: this.text
}
});
See this bin for an example: http://jsbin.com/kipato/2/edit?html,js,console,output
In the stamped template there are two inputs bound to two variables which trigger instanceProp and instancePath. Unfortunately I've been unable to fix the error thrown when the latter happens.

how to dynamically append an element to dom-if in Polymer?

My goal is to append an element to existing dom-if dynamically. Problem is that after appending I can see appended element in the DOM three but it never reacts on condition and stays always hidden.
<template>
<template id="domif" is="dom-if" if="[[condition]]" restamp></template>
</template>
ready() {
var el = document.createElement("input");
Polymer.dom(this.$.domif).appendChild(el);
Polymer.dom.flush();
}
Exploring DOM with hardcoded dom-if and input shows that <input /> element is actually not a child of dom-if but lives next to it..
<template>
<template is="dom-if" if="[[condition]]" restamp>
<input />
</template>
</template>
That gave me a clue that I probably should append my element next to dom-if... But now the biggest question is how to say to dom-if that appended element should be rendered if condition is satisfied. Any ideas?
How about adding a span in your dom-if and appending it to that span?
Update after some comments : We need to use this.async for the item to be found. Using the ready-event only works when the condition is true initially. So you could append the element in a conditionChanged-observer - this is a working example :
<dom-module id='my-element1'>
<template>
<template is="dom-if" if="[[condition]]" restamp>
<span id="appendHere"></span>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element1',
properties: {
condition: {
type: Boolean,
observer: "_conditionChanged"
}
},
_conditionChanged: function(newVal) {
if (newVal) {
this.async(function() {
var el = document.createElement("input");
Polymer.dom(this.$$("#appendHere")).appendChild(el);
Polymer.dom.flush();
});
}
}
});
</script>
Try it here : http://plnkr.co/edit/1IIeM3gSjHIIZ5xpZKa1?p=preview .
A side-effect of using dom-if in this case is that after setting the condition to false, the element disappears completely and gets added on the next condition-change again. So every change before setting the condition to false gets lost. You could work around it by putting the added element somewhere hidden when the condition changes and getting it back later, but I don't think this is a good idea, if the following is an alternative :
The Polymer-team recommends using dom-if only if there is no other way, like hiding the element. So, if it is possible you also could do something like this (condition has to be true to hide the element) :
<dom-module id='my-element1'>
<template>
<span id="appendHere" hidden$="[[condition]]"></span>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element1',
properties: {
condition: Boolean
},
ready: function() {
var el = document.createElement("input");
Polymer.dom(this.$.appendHere).appendChild(el);
Polymer.dom.flush();
}
});
</script>
Try it here :
http://plnkr.co/edit/mCtwqmqtCPaLOUveOqWS?p=preview
The template element itself will not be added to the DOM, this is the reason you can't access it using querySelector or getElementXxx

How do I catch that enter is pressed in Polymer 1.0 paper-input

How do I catch when enter is pressed in Polymer 1.0 paper-input?
I tried with the on-bind-value-changed that is exposed through the iron-input, but it seems it only differentiates with letters in the event argument where e.detail is null on all other keys, such as enter, tab, etc.
I would bind a keydown event to the input that called a function. In there you can find which key was pressed. For example:
<dom-module id="test-element">
<template>
<!-- add keydown listener to paper input -->
<paper-input label="Input label" on-keydown="checkForEnter"></paper-input>
</template>
<script>
Polymer({
is: "test-element",
checkForEnter: function (e) {
// check if 'enter' was pressed
if (e.keyCode === 13) {
// enter pressed!
}
}
});
</script>
</dom-module>
Another possibility would be to use iron-a11y-keys. That way, you could declaratively define what's happening when the user presses the enter key while the focus is on the paper-input element.
Example (copied from the Polymer Catalog):
<iron-a11y-keys id="a11y" target="[[target]]" keys="enter"
on-keys-pressed="onEnter"></iron-a11y-keys>
<paper-input id="input" placeholder="Type something. Press enter. Check console." value="{{userInput::input}}"></paper-input>
After that, you will have to bind the target property of the a11y element to the paper-input element, like so:
...
properties: {
userInput: {
type: String,
notify: true,
},
target: {
type: Object,
value: function() {
return this.$.input;
}
},
},
onEnter: function() {
console.log(this.userInput);
}
...
Hope that helps. See iron-a11y-keys for more information.

paper-dropdown searchable / filterable

Is there any component like the paper-dropdown element with any extra line to search and filter the items of the drop down? In Jquery there are tons of such elements.
It would be really cool if polymer has something like that to or if anyone can give me a hint, how I can achieve that on my own.
Thanks!!
Check out #addyo's <typeahead-country> element.
https://github.com/addyosmani/typeahead-country
You could fork it and change the country list to be whatever elements you need.
All you have to do is inside paper-listbox add a paper-input and then connect its value with a filer that repeats paper-item for dropdown. You will also need to stop propagation of events that happens on paper-input.
HTML
<paper-dropdown-menu label="Fruits">
<paper-listbox class="dropdown-content" attr-for-selected="data-value" selected="{{fruit}}">
<paper-input class="paperdropdownsearch" label="Search" value="{{key}}"
on-tap="_stopEventPropagation"
on-keydown="_stopEventPropagation"
on-keyup="_stopEventPropagation"></paper-input>
<template is="dom-repeat" items="{{allFruits}}" filter="{onMatch(key)}}">
<paper-item data-value="{{item}}">{{item}}</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
SCRIPT
onMatch: function (key) {
if (!key) {
return null;
} else {
try {
key = key.toLowerCase();
} catch (err) {}
return function (item) {
var curr = item.toLowerCase();
if (curr.search(key) >= 0) {
return true;
}
};
}
},
_stopEventPropagation: function(e) {
e.stopPropagation();
},
Demo
http://embed.plnkr.co/ax9gjxonA3rC8K4Xr2LL/

Remove child element's attribute from Polymer js

I've a custom element which, among other things, has a core-input and a paper button in it.
When the element is created, the input is disabled, and I want to enable it when I tap the button.
I've tried several ways and can't access the input's attribute.
<paper-input-decorator label="Nombre de usuario" floatingLabel>
<input id="usernameinput" value="{{UserName}}" is="core-input" disabled />
</paper-input-decorator>
<paper-button raised id="edprobutton" on-tap="{{edbutTapped}}">EDITAR</paper-button>
What should I write in
edbutTapped: function () {
},
EDIT
So, I've learned that the problem was that my username input element was inside a repeat template, and that's bad for what I was trying to do. Now I'm trying to bind a single json object to my element, with no luck so far.
What I have right now:
In my Index page:
<profile-page id="profpage" isProfile="true" entity="{{profEntity}}"></profile-page>
<script>
$(document).ready(function () {
var maintemplate = document.querySelector('#fulltemplate');
$.getJSON('api/userProfile.json', function (data) {
var jsonString = JSON.stringify(data);
alert(jsonString);
maintemplate.profEntity = jsonString;
});
}
</script>
In my element's page:
<polymer-element name="profile-page" attributes="isprofile entity">
<template>
<style>
[...]
</style>
<div flex vertical layout>
<core-label class="namepro">{{entity.Name}}</core-label>
<core-label class="subpro">{{entity.CompanyPosition}}</core-label>
<core-label class="subpro">{{entity.OrgUnitName}}</core-label>
</div>
</template>
</polymer-element>
And my JSON looks like this:
{"Name": "Sara Alvarez","CompanyPosition": "Desarrollo","OrgUnitName": "N-Adviser"}
I'm asuming I need to "update" my element somehow after changing its entity attribute?
Try the following
<script>
Polymer({
edbutTapped: function () {
this.$.usernameinput.disabled = false;
}
});
</script>
The this.$ allows you to access controls defined in an elements and the usernameinput is the id you assigned to the input.
This can go below the closing tag of the element you are defining.
'disabled' is conditional-attribute.
So this will be the correct use of it:
<input id="usernameinput" value="{{UserName}}" is="core-input" disabled?="{{isDisabled}}" />
In the prototype:
//first disable the field, can be done in ready callback:
ready: function () {
this.isDisabled = 'true';
}
//set idDisabled to 'false' i.e. enable the input
edbutTapped: function () {
this.isDisabled = 'false';
},
OK this is going to be a long answer (hence why I am not entering this as an edit of my original answer). I've just done something which is functionally the same.
The first thing is this code;
$.getJSON('api/userProfile.json', function (data) {
var jsonString = JSON.stringify(data);
alert(jsonString);
maintemplate.profEntity = jsonString;
});
Polymer has a control called core-ajax - this as it's name suggests makes an ajax call. The other really nice thing is that it can be made to execute when the URL changes. This is the code from the project I've got.
<core-ajax id="ajax"
auto=true
method="POST"
url="/RoutingMapHandler.php?Command=retrieve&Id=all"
response="{{response}}"
handleas="json"
on-core-error="{{handleError}}"
on-core-response="{{handleResponse}}">
</core-ajax>
The auto is the bit which tells it to fire when the URL changes. The description of auto from the polymer documentation is as follows;
With auto set to true, the element performs a request whenever its
url, params or body properties are changed.
you don't need the on-core-response but the on-core-error might be more useful. For my code response contains the JSON returned.
So for your code - it would be something like this
<core-ajax id="ajax"
auto=true
method="POST"
url="/api/userProfile.json"
response="{{jsonString}}"
handleas="json"
on-core-error="{{handleError}}" >
</core-ajax>
Now we have the data coming into your project we need to handle this. This is done by making use of Polymer's data-binding.
Lets detour to the element you are creating. Cannot see anything wrong with the following line.
<polymer-element name="profile-page" attributes="isprofile entity">
We have an element called 'profile-page' with two properties 'isprofile' and 'entity'.
Only because my Javascript leaves a bit to be desired I would pass each property as a seperate entity making that line
<polymer-element name="profile-page" attributes="isprofile name companyposition OrgUnitName">
Then at the bottom of your element define a script tag
<script>
Polymer({
name: "",
companyposition: "",
OrgUnitName: ""
});
</script>
Now back to the calling (profile-page). The following code (from my project) has the following;
<template repeat="{{m in response.data}}">
<map-list-element mapname="{{m.mapName}}" recordid="{{m.Id}}" on-show-settings="{{showSettings}}">
</map-list-element>
</template>
Here we repeat the following each element. In your case you only have one entry and it is stored in jsonString so your template is something like this
<template repeat="{{u in jsonString}}">
<profile-page name="{{u.name}} companyposition="{{u.companyposition}}" OrgUnitName="{{u.OrgUnitName}}">
</profile-page>
</template>
Now we get to the issue you have. Return to your profie-page element. Nothing wrong with the line
on-tap="{{edbutTapped}}"
This calls a function called edbutTapped. Taking the code I gave you earlier
<script>
Polymer({
edbutTapped: function () {
this.$.usernameinput.disabled = false;
}
});
</script>
The only thing to change here is add the following code
created: function() {
this.$.usernameinput.disabled = true;
},
This is inserted after the Polymer({ line. I cannot see in your revised code where the usernameinput is defined but I am assuming you have not posted it and it is defined in the element.
And you should be working, but remember to keep your case consistent and to be honest I've not been - certain parts of Polymer are case sensitive - that catches me out all the time :)