bnavetta / aurelia-polymer

Aurelia plugin to support Polymer
MIT License
18 stars 5 forks source link

paper-tabs repeat-for issue #17

Closed dpinart closed 8 years ago

dpinart commented 8 years ago

Hi I have a view with a paper-tabs element. The paper-tabs contains a repeat.for that creates a set of paper-tab items similar to:

<paper-tabs>
    <paper-tab repeat.for="item of items">${item.text}</paper-tab>
</paper-tabs>

Despite at first look it seems above snippet works fine it doesn't. Parent paper-tabs items property is empty, so interacting with any paper-tab children does not has any effect on the paper-tabs as it doesn't belongs to the paper-tabs.

I suspect a similar issue exists with paper-toolbar.

Is there any way to dynamically populate a paper-tabs element?

Thanks

swiftanthony commented 8 years ago

You can make it work by creating elements dynamically polymer way:

// Create tabs element
let tabs = document.createElement('paper-tabs');
Polymer.dom(yourContainerElement).appendChild(menu);

// Generate tabs
for (let i = 0; i < tabs.length; i++) {
    let tab = document.createElement('paper-tab');
    Polymer.dom(tab).textContent = 'Tab title ' + i;
    Polymer.dom(tabs).appendChild(tab);    
}
dpinart commented 8 years ago

hmm, thanks, it's interesting.

Does anybody know how to attach custom attributes progamatically to HTML Elements?

I've found a more "aurèlia" friendly approach, not ideal though. I have created a viewModel with no view that dynamically builds the template and returns an InlineViewStrategy. Somethinglike(typescript):

export class TabPanelViewModel{
public actions: ActionItem[];
public activate(actions: ActionItem[]){
this.actions = actions;
}
public getViewStrategy(){
let template = '<template><paper-tabs selected="0">';
    for(let i = 0; i < this.actions.length; i++){
      template += '<paper-tab><compose view-model.bind="actions[' + i + '].viewModel" model.bind="actions[' + i +'].model"></compose></paper-tab>';
    }
    template += '</paper-tabs></template>';
    return new InlineViewStrategy(template);
}
}

This way, the markup contains a paper-tabs with all paper-tab items, so when is added to the DOM polymer paper-tabs knows about its children.

Then, at view:

<compose class="bottom self-end fit" view-model="'./tab-panel}" model.bind="items"></compose>

I'm using the same pattern for paper-toolbar and it works fine too

k2s commented 8 years ago

my experience is that Polymer requires sometimes that you use its DOM manipulation (append, remove) functions to make components work correctly.

so the InlineViewStrategy may look more Aurelia way, but if you will try for example dynamically change the tabs it will again not work if you don't use Polymer/append methods.

dpinart commented 8 years ago

Well, I implemented an small hack to force the ViewModel to reload, basically, the view-model in compose element has bind with a condition like

<compose class="bottom self-end fit" view-model="{items.length ? './tab-panel' : './empty'}" model.bind="items"></compose>

'empty' is just an empty template, so if I want to rebind tabs I have to clear items array and then repopulate it with the new items.

It's not the more elegant solution and obviously I'd prefer the repeat.for option working properly, but I think right now I prefer InlineViewStrategy over Polymer DOM manipulation because it allows more flexibility and the syntax is the same I use for other templates. (And thanks to JSX support in many IDEs typing the template in .js files it's quite easy and no so error proune

Obviously, I can find scenarios where InlineViewStrategy does no fit well and I have to use Polymer programatically.

Anyway It's nice we can offer several workarounds for this issue...

bnavetta commented 8 years ago

I think it's the same thing as #12, where Aurelia adds a template element that the Polymer elements are written to ignore. The <template> shows up using Polymer.dom but not in Chrome's inspector, I'm guessing since Polymer.dom shows the shadow DOM web component elements and Chrome expands them out with the generated HTML. The IronSelectable behavior uses Polymer.dom.queryDistributedElements, which doesn't seem to peek into the <template>, to find items, so it only sees that template element, which it ignores.

PolymerElements/iron-selector#42 might fix it.

HIRANO-Satoshi commented 8 years ago

The following example seems working for me. So, I'm not sure what the matter is. Bellow might not make sense.

    <paper-tabs>
      <paper-tab repeat.for="item of items">${item}</paper-tab>
    </paper-tabs>

This one does not because paper-tabs does not recognize sub children of the child element,

.

      <paper-tabs>
        <div>
          <paper-tab repeat.for="item of items">${item}</paper-tab>
        </div>
      </paper-tabs>

I made au-select attribute that gathers all children and sub children under or similar elements. I tried paper-tabs as well.

In the following example, paper-tabs works au-select with

. Give selectable="CSS selector" if you want to gather elements with the selector.

      <paper-tabs au-select selectable=".selectable_item">
        <div>
          <paper-tab class="selectable_item" repeat.for="item of items">${item}</paper-tab>
        </div>
      </paper-tabs>
// resources/au-select-custom-attribute.ts
//    add  aurelia.use.plugin('resources/au-select-custom-attribute') to main.ts
import { autoinject } from 'aurelia-framework';

@autoinject
export class AuSelectCustomAttribute {
    constructor(private element: Element) {
        (<any>element)._updateItems = function() {
            //var nodes = (<any>window).Polymer.dom(this).queryDistributedElements(this.selectable || '*');
            var nodes = (<any>window).Polymer.dom(this).querySelectorAll(this.selectable || '*');
            nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
            this._setItems(nodes);
        }
    }   
}

The above code was borrowed from PolymerElements/iron-selector#42.

Or, the following code can be added to main.ts to replace the default behavior, if you don't want to use the au-select attribute.

export function configure(aurelia: Aurelia) {
  (<any>window).Polymer.IronSelectableBehavior._updateItems = function() {
      //var nodes = win.Polymer.dom(this).queryDistributedElements(this.selectable || '*');
     var nodes = (<any>window).Polymer.dom(this).querySelectorAll(this.selectable || '*');
      nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
      this._setItems(nodes);
    }
bnavetta commented 8 years ago

Closing this since au-select seems to work, but reopen if it's still an issue