How to access and modify dom-repeat children dynamically - polymer

Using Polymer 1.0, suppose I have some elements nested in a dom-repeat element, such as the following:
<div id="stuffDiv">
<template is="dom-repeat" items="{{myItems}}">
<iron-icon icon="star" on-tap="onTap"></iron-icon>
</template>
</div>
I'd like to change an attribute, say class, on all the iron-icons within the "onTap" method. So I've figured out the following implementation
onTap: function(e) {
var index = e.model.index;
var stuffIcons = Polymer.dom(this.$.stuffDiv).querySelectorAll('iron-icon');
for (var i = 0; i <= index; ++i) {
this.toggleClass('magicClass', true, stuffIcons[i]);
}
for (var i = index+1; i < stuffIcons.length; ++i) {
this.toggleClass('magicClass', false, stuffIcons[i]);
}
},
This works, but feels very clunky. Is there a better approach that's not well documented? I don't see anything obvious on Polymer 1.0's resources.
Note, i've also tried having iron-icon's class depend on an item value and dynamically update that in onTap using this.set(...), however that doesn't work either, the class values I set get obliterated for reasons unknown to me.

You could look at binding the magicClass class.
Is this the result you were after?
<div id="stuffDiv">
<template is="dom-repeat" items="{{myItems}}">
<iron-icon icon="star" on-tap="onTap" class$="{{getIconClass(index, stopTheMagicIndex)}}">Star</iron-icon>
</template>
</div>
And then...
properties: {
myItems: {
type: Array,
value: function() {
return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}
},
stopTheMagicIndex: Number
},
onTap: function(e) {
this.set('stopTheMagicIndex', e.model.index);
},
getIconClass: function(index) {
if (index <= this.stopTheMagicIndex) return 'magicClass';
return '';
}

Related

Assigning value to variables in polymer

Polymer doesn't bind data if we assign value to polymer multiple times.
For example:
Polymer({
is: "g-feed",
properties: {
event: String
},
ready: ()=> {
var self = this;
self.news = [];
var nws = [];
nws.push({nm:'One'});
nws.push({nm:'Two'});
self.news = nws;
nws.push({nm:'One1'});
nws.push({nm:'Twoa'});
self.news = nws;
console.log(self.news);
}
});
Here the news array would only hold the values "one" and "two" rendered on the webpage.
Can you please tell me why this happens and how to overcome this. Also, how to work with consistantly changing data in polymer.
Thanks.

draggable rows in a p:dataTable by handle

I have a DataTable in which I added drag and drop support for the rows (draggableRows="true"). The problem is that wherever I click inside a row, I can drag it.
What I want is the possibility to drag the row only by a handle, the handle could be a column field with an icon at the left of the row for example (have a look at the screenshot), so if the user clicks on a row outside of the handle, there's no drag support; but if he clicks on the handle, he'll have the possibility to drag the entire row.
How could I implement this?
The source is always with you. In there you can see the makeRowsDraggable function on line 2727 in datatable.js
makeRowsDraggable: function() {
var $this = this;
this.tbody.sortable({
placeholder: 'ui-datatable-rowordering ui-state-active',
cursor: 'move',
handle: 'td,span:not(.ui-c)',
appendTo: document.body,
start: function(event, ui) {
ui.helper.css('z-index', ++PrimeFaces.zindex);
},
...
}
with a reference to the handle ('td, span:not(.ui-c)').
By overriding this function and having the handle point to a selector that explicitly refers to your handle, you can 'fix' it.
You can even make this generic by not assigning an explict string to the handle, but but looking it up on e.g. a custom pass-through attribute you define on the datatable where you put the 'string' in.
Did I mention already that the source is always with you? Good thing to remember when having further questions
Since Primefaces 6.2 p:datatable has a property rowDragSelector specifically for this purpose. See the example below:
<p:dataTable value="#{myBean.entities}" id="myTablePreferredId" rowDragSelector=".draggableHandle" draggableRows="true">
<p:ajax event="rowReorder" listener="#{myBean.onRowReorder}"/>
<p:column>
<h:outputText styleClass="fa fa-arrows-v draggableHandle" />
</p:column>
...
</p:dataTable>
For more details refer to the primefaces documentation.
My solution is the same as the solution of #Kuketje.
Here is the source code (compatible with Primefaces 6.1)
if (PrimeFaces.widget.DataTable){
PrimeFaces.widget.DataTable = PrimeFaces.widget.DataTable.extend({
makeRowsDraggable: function () {
var $this = this,
draggableHandle = '.dnd-handle'; //change to what ever selector as you like
this.tbody.sortable({
placeholder: 'ui-datatable-rowordering ui-state-active',
cursor: 'move',
handle: draggableHandle,
appendTo: document.body,
start: function (event, ui) {
ui.helper.css('z-index', ++PrimeFaces.zindex);
},
helper: function (event, ui) {
var cells = ui.children(),
helper = $('<div class="ui-datatable ui-widget"><table><tbody></tbody></table></div>'),
helperRow = ui.clone(),
helperCells = helperRow.children();
for (var i = 0; i < helperCells.length; i++) {
helperCells.eq(i).width(cells.eq(i).width());
}
helperRow.appendTo(helper.find('tbody'));
return helper;
},
update: function (event, ui) {
var fromIndex = ui.item.data('ri'),
toIndex = $this.paginator ? $this.paginator.getFirst() + ui.item.index() : ui.item.index();
$this.syncRowParity();
var options = {
source: $this.id,
process: $this.id,
params: [
{name: $this.id + '_rowreorder', value: true},
{name: $this.id + '_fromIndex', value: fromIndex},
{name: $this.id + '_toIndex', value: toIndex},
{name: this.id + '_skipChildren', value: true}
]
}
if ($this.hasBehavior('rowReorder')) {
$this.cfg.behaviors['rowReorder'].call($this, options);
}
else {
PrimeFaces.ajax.Request.handle(options);
}
},
change: function (event, ui) {
if ($this.cfg.scrollable) {
PrimeFaces.scrollInView($this.scrollBody, ui.placeholder);
}
}
});
}
});
}
The solution from Vít Suchánek is not really working. It detects the drag&drop handle only when the page is ready. After the first drag&drop interaction, it is not going to work anymore.
Another possibility is to override Primefaces's setting of handler after initialization of UI sortable:
<script type="text/javascript">
$(document).ready(function() {
var sortableRows = $(".tableWithDraggableRows > tbody");
if (sortableRows) {
sortableRows.sortable("option", "handle", ".ui-icon-arrow-4");
}
});
</script>
See http://api.jqueryui.com/sortable/#option-handle

Polymer 1.0 - data-bound arrays not updating correctly

Demo here: http://jsbin.com/wiqowo/15/edit?html,output
When I loop through this.data in the example below, it iterates over the correct number of items, however the value of the individual items is not output. Is this a bug, or am I missing something?
<dom-module id="test-element">
<template>
<h3>data</h3>
<p>data: <span>{{data}}</span></p>
<ul>
<template is="dom-repeat" items="{{data}}">
<li>{{item}}</li>
</template>
</ul>
</template>
<script>
Polymer({
ready: function() {
this.data = ['Item #1', 'Item #2', 'Item #3'];
for (var i=this.data.length; i<10; i++) {
this.data.push('Item #' + (i+1));
}
console.log(this.data);
}
});
</script>
</dom-module>
Figured it out. Apparently array mutation is not supported in 1.0. You have to call the new Polymer.push, Polymer.pop, etc. methods.
Demo: http://jsbin.com/wiqowo/25/edit?html,output
for (var i=this.data.length; i<10; i++) {
this.push('data', 'Item #' + (i+1));
}
Define an array var inside of the the ready function, and push data to it. Then, assign it to this.data.
Polymer({
ready: function() {
var localData = ['Item #1', 'Item #2', 'Item #3'];
for (var i=localData.length; i<10; i++) {
localData.push('Item #' + (i+1));
}
this.data = localData;
console.log(this.data);
}
});

Polymer 1.0 template bind ref equivalent

In Polymer 0.5, you could do:
<template bind ref="howdyTpl"></template>
...
<template id="howdyTpl">Howdy {{whatever}}</template>
And the template with id 'howdyTpl' would get stamped where referenced in the ref attribute.
How can I do something similar in Polymer 1.0?
Here's a custom element that does something similar.
Polymer({
is: 'bind-ref',
properties: {
ref: {
type: String,
observer: 'refChanged',
},
},
refChanged: function(newRef, oldRef) {
if (oldRef != undefined) {
this._removeChildren();
this._stamp();
}
},
_stamp: function() {
this._parent = Polymer.dom(this).parentNode;
var rootEl = this._parent;
while (Polymer.dom(rootEl).parentNode) {
rootEl = Polymer.dom(rootEl).parentNode;
}
var tpl = Polymer.dom(rootEl).querySelector('#' + this.ref);
var tplRoot = tpl.stamp().root;
this._children = Array.prototype.slice.call(tplRoot.childNodes);
Polymer.dom(this._parent).insertBefore(tplRoot, this);
},
_removeChildren: function() {
for (var i = 0; i < this._children.length; i++) {
Polymer.dom(this._parent).removeChild(this._children[i]);
}
},
attached: function() { this._stamp(); },
detached: function() { this._removeChildren(); },
});
The target template element must be a dom-template element.
<bind-ref ref="howdyTpl"></bind-ref>
<template is="dom-template" id="howdyTpl">Howdy <span>{{whatever}}</span></template>
(Code snippets licensed Apache 2.0.)
I've created an element that resolves this problem. It's kind of a hack, but it works... including with multi-depth file inclusion and data binding that the other answer provided did not handle.

How To Access Polymer Custom Element From Callback

I need to access the custom element and call its method from the click event callback.
<polymer-element name="my-element">
<template>
<style type="text/css" media="screen">
...
</style>
<ul id="my_data"></ul>
</template>
<script>
Polymer('my-element', {
dataSelected: function(selectedText) {
//...
},
setData: function(data) {
for (var i = 0; i < data.length; i++) {
var li = document.createElement('li');
li.addEventListener('click', function(e) {
// how can I call dataSelected() from here?
});
li.innerText = data[i];
this.$.my_data.appendChild(li);
}
}
});
</script>
</polymer-element>
How can I call the custom element's dataSelected() method from the callback?
You can use bind to attach a this context to any function, so:
li.addEventListener('click', function(e) {
this.dataSelected(e.target.innerText);
}.bind(this));
http://jsbin.com/xorex/4/edit
But you can make things easier by using more Polymer sugaring. For example, you can publish data and use the observation system, like so:
<polymer-element name="my-element" attributes="data">
...
data: [], // type hint that data is an array
...
dataChanged: function() { // formerly setData
http://jsbin.com/xorex/5/edit
Also, you can use the built-in event system instead of addEventListener
<polymer-element name="my-element" attributes="data">
...
<ul id="my_data" on-tap="{{dataTap}}"></ul>
...
dataTap: function(e) { // `tap` supports touch and mouse
if (e.target.localName === 'li') {
this.dataSelected(e.target.textContent);
}
}
http://jsbin.com/xorex/6/edit
But the biggest win is using <template repeat> instead of creating elements in JavaScript. At that point, the complete element can look like this:
<polymer-element name="my-element" attributes="data">
<template>
<ul id="my_data">
<template repeat="{{item in data}}">
<li on-tap="{{dataTap}}">{{item}}</li>
</template>
</ul>
</template>
<script>
Polymer('my-element', {
data: [],
dataTap: function(e) {
console.log('dataSelected: ' + e.target.textContent);
}
});
</script>
</polymer-element>
http://jsbin.com/xorex/7/edit
You could insert element = this; at the beginning of your setData() function and call element.dataSelected(); in the event handler.
But i think for what you want to achieve, you'd better use a repeat template (Iterative templates) and a direct binding to your click handler function (Declarative event mapping).