paper-dropdown searchable / filterable - polymer

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/

Related

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

Repeat light dom element

In my component I would like to repeat a list of item with template provided by the light dom of the component. For example:
<template is="dom-repeat" items="{{items}}">
<content select="#itemTemplate"></content>
</template>
However, it seems that Polymer only inserts the light dom element #itemTemplate exactly one time instead of multiple times. Is there other way to repeat a light dom element?
I have created a simple prototype, that lets you specify the number of repetitions of the light DOM template.
Because the content is in the light DOM, you can style it from the outside as you would usually do. And data binding inside the template also works, since I have implemented the _forwardParentProp, and _forwardParentPath methods from the Templatizer.
Be aware, that I have not implemented the instance specific properties, which would allow per row specific variables, such as index and item. This can, of course, be done, but would need a bit more work.
See the prototype in action: JSBin.
OK, let's go into details:
The usage of the test-element along with data-binding to both input elements is fairly straightforward:
<template is="dom-bind">
Number of repeats: <input type="text" value="{{repeats::input}}" /> <br />
Custom message: <input type="text" value="{{customMessage::input}}" />
<test-element repeats="{{repeats}}">
<template>
<h1>Title!</h1>
<p>
Custom message: <em>[[customMessage]]</em>
</p>
</template>
</test-element>
</template>
Notice the dom-bind, which is needed to create a data-binding scope.
As for the test-element, the whole source code looks like this:
<dom-module id="test-element">
<template>
<style>
:host {
display: block;
}
</style>
<content></content>
</template>
<script>
Polymer({
is: 'test-element',
behaviors: [
Polymer.Templatizer,
],
properties: {
repeats: {
type: Number,
value: 3,
notify: true,
},
},
observers: [
'_repeatsChanged(repeats)',
],
_repeatsChanged: function(repeats) {
// First time only: initialize template
if (this.template === undefined) {
this.template = Polymer.dom(this).querySelector('template');
this.templatize(this.template);
}
// Remove previously stamped children
while (Polymer.dom(this).firstChild) {
Polymer.dom(this).removeChild(Polymer.dom(this).firstChild);
}
// Stamp new ones
this.stamped = new Array(repeats);
var inst;
for (var i = 0; i < repeats; i++) {
inst = this.stamp(null);
this.stamped[i] = inst.root.querySelector('*');
Polymer.dom(this).appendChild(inst.root);
}
},
// Copied from iron-list
_forwardParentProp: function(prop, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance[prop] = value;
}, this);
}
},
// Copied from iron-list
_forwardParentPath: function(path, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance.notifyPath(path, value, true);
}, this);
}
},
});
</script>
</dom-module>
There is only one property, repeats, which specifies the number of stamped instances. Default value is 3. To accomodate changes of said property's value, a observer has been created. This is also the place where the stamping takes place:
_repeatsChanged: function(repeats) {
// First time only: initialize template
if (this.template === undefined) {
this.template = Polymer.dom(this).querySelector('template');
this.templatize(this.template);
}
// Remove previously stamped children
while (Polymer.dom(this).firstChild) {
Polymer.dom(this).removeChild(Polymer.dom(this).firstChild);
}
// Stamp new ones
this.stamped = new Array(repeats);
var inst;
for (var i = 0; i < repeats; i++) {
inst = this.stamp(null);
this.stamped[i] = inst.root.querySelector('*');
Polymer.dom(this).appendChild(inst.root);
}
},
Firstly (and only once), the template is read from the light DOM and
the templatize method is called. This method initializes the
Templatize behavior.
Secondly, all previously stamped children are removed (so that the
elements don't just build up infinitely).
Thirdly, new children are stamped, according to the current value of
repeats. All stamped instances are saved to this.stamped, which
is needed for the data-binding from the outside to work.
Last but not least, the Templatizer behavior is implemented via two methods (and two are left unimplemented):
// Copied from iron-list
_forwardParentProp: function(prop, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance[prop] = value;
}, this);
}
},
// Copied from iron-list
_forwardParentPath: function(path, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance.notifyPath(path, value, true);
}, this);
}
},
Both methods are taken from the iron-list. They iterate through the stamped children and propagate property changes and path notifications.
You can include your content in a separate element and use it.
<template is="dom-repeat" items={{items}}">
<child-element item=[[item]]></child-element>
</template>

Can't find element on iron ajax event methods

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. ;)

How do I put the selected core-menu item into a custom polymer element?

I'm trying to encapsulate a paper-dropdown in a paper-button. To do this, I made a custom element, paper-dropdown-holder:
<polymer-element name="paper-dropdown-holder" extends="paper-button" relative on-tap="{{toggle}}">
<template>
<shadow></shadow>
<content></content>
</template>
<script>
Polymer({
toggle: function() {
if (!this.dropdown) {
this.dropdown = this.querySelector('paper-dropdown');
}
this.dropdown && this.dropdown.toggle();
}
});
</script>
</polymer-element>
and I'm using it in the page like:
<paper-dropdown-holder raised tabindex="0" class="unpadded">
<paper-dropdown class="dropdown" flex>
<core-menu class="menu" selected="0">
<paper-item>Writing</paper-item>
<paper-item>Blog</paper-item>
<paper-item>Art</paper-item>
</core-menu>
</paper-dropdown>
</paper-dropdown-holder>
My problem is deciphering The documentation to figure out how to automatically put the text of the currently-selected menu item into the paper-dropdown-holder.
My first attempt was to just use a standard paper-dropdown-menu, but I couldn't as easily style that like a paper-button. Is there any way to do this that's not (for lack of a better term) hacky? I'd love if the answer would keep to the Polymer philosophies.
Bonus challenge: How do I set default text like "choose section"?
One of awesome things of Polymer is it's open source... that said you could learn how to implement new element based on already existing elements....
If you have a look at paper-dropdown-menu source you could easily make something like it but with paper-button as a "control".
So
The new element should extend core-dropdown-base not
paper-button.
To make that element logically working you could do that with
some help of paper-dropdown-menu by binding (core-overlay-open,
core-activate, core-select) events to the according handlers.
(the actual binding happens in core-dropdown-base in dropdown
getter which called inside attached event listener.
To put them together:
<polymer-element name="paper-dropdown-holder" extends="core-dropdown-base" relative>
<template>
<div>
<paper-button raised on-tap="{{toggle}}">{{selectedItemLabel || label}}</paper-button>
<content></content>
</div>
</template>
<script>
Polymer('paper-dropdown-holder', {
publish: {
label: 'Select an item',
},
selectedItemLabel: '',
overlayListeners: {
'core-overlay-open': 'openAction',
'core-activate': 'activateAction',
'core-select': 'selectAction'
},
activateAction: function(e) {
this.opened = false;
},
selectAction: function(e) {
var detail = e.detail;
if (detail.isSelected) {
this.selectedItemLabel = detail.item.label || detail.item.textContent;
} else {
this.selectedItemLabel = '';
}
}
});
</script>
</polymer-element>
Demo.

Polymer ondblclick not working

I am trying to use ondblclick inside polymer element, but I cannot get it to work.
I know that I can just use ondblclick="someFunction()" but I need to access double clicked element model, something like on-tap event:
event.target.templateInstance.model.myModel;
<script src="http://www.polymer-project.org/platform.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="dblclick-test">
<template>
<button on-dblclick="{{btnDblClick}}" on-tap="{{btnTap}}">{{btnText}}</button>
</template>
<script>
Polymer({
btnText: 'double click me',
btnDblClick: function(event) {
// in real code I need to use
// event.target.templateInstance.model
// just like on-tap's event param
this.btnText = 'Hi, u dbl clicked me !!!!!!';
},
btnTap: function(event) {
this.btnText = 'Hi, u clicked me !';
}
})
</script>
</polymer-element>
<dblclick-test></dblclick-test>
Is there any polymeric way to do it?
First
I was missing the declarative event mapping by using ondbleclick instead of on-dblclick, thanks to ebidel edit to my question.
Even though the above example won't work.
Second
To solve the conflict between on-tap and on-dblclick, I used something like the following in my code:
<script src="http://www.polymer-project.org/platform.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="dblclick-test">
<template>
<button on-tap="{{btnTap}}">{{btnText}}</button>
</template>
<script>
Polymer({
btnText: 'double click me',
btnDblClick: function(event) {
this.btnText = 'Hi, u dbl clicked me !!!!!!';
},
btnTap: function(event) {
if (this.tappedOneTime) {
this.btnDblClick();
this.tappedOneTime = false;
} else {
this.btnText = 'Hi, u clicked me !';
}
this.tappedOneTime = true;
// reset flag after 800ms
this.async(function() {
this.tappedOneTime = false;
}, null, 800);
}
})
</script>
</polymer-element>
<dblclick-test></dblclick-test>
I assume tap is also fired on dblclick this makes it difficult.
My (and other answers) to this question Distinguish between onClick and onDoubleClick on same element to perform different actions in Dart show possible solutions.
Don't use on-tap and on-dblclick together, if you need both behaviors use on-click instead on-tap.
<my-elem on-click="clicked" on-dblclick="dblclicked">