Search code examples
polymerweb-component

Creating element with configurable output


I am writing a simple widget that will create an output based on fetched data (taken from an AJAX request). This version of the my-element is the non-configurable, standard one:

http://jsbin.com/rivala/edit?html,output#H:L56

Thing is, I want the user to be able to decide what the output will look like. Since Polymer doesn't allow us to extend existing elements, I went the other way around: I create a behaviour (err... excuse me, a behavior, it's so hard not to type that "u" every time) that does most of the work. Here is my result:

http://jsbin.com/yuxecu/edit?html,output

So, in order to create create an element, all the user needs to do is:

<dom-module id="my-element">
  <template>

    <!-- THE FOLLOWING PART IS THE ONLY THING THE USER WILL CHANGE -->
    <paper-dropdown-menu label="Your favourite category">
      <paper-menu class="dropdown-content">

        <template is="dom-repeat" items="{{_data}}">  
          <paper-item>{{item.name}}</paper-item>
        </template>

    </paper-dropdown-menu>

  </template>

  <script>
    Polymer({
      is: "my-element",
       behaviors: [ MyBehaviour],
     })
  </script>
</dom-module>

And then use it:

I would have much much preferred something a little easier. For example, it would have been much nicer to allow something like this:

<my-element url="http://output.jsbin.com/zonona/3.js">

  <template id="bindme">

    <!-- THE FOLLOWING PART IS THE ONLY THING THE USER WILL CHANGE -->
    <paper-dropdown-menu label="Your favourite category">
      <paper-menu class="dropdown-content">

        <template is="dom-repeat" items="{{_data}}">  
          <paper-item>{{item.name}}</paper-item>
        </template>

    </paper-dropdown-menu>
   </template>
</my-element>

But I tried and tried and then tried some more, and it doesn't seem to be possible unless you really want to get your hands dirty.

Once extending non-native elements is possible, I assume I can just create an element declaratively that extends my-element and defines a new template. Till then...

Questions:

  • Does my code seem to be following at least roughly Polymer's best practices?

  • Is there a much easier way to do this, that I didn't think of?

  • Any more comments?

Thank you as ever...


Solution

  • I don't know what I am doing is quite the same thing, but you might be able to draw inspiration from it. I have created a generic dialog box that will provide the results from a database query in it, with the headings data driven and the row size and content also data driven. I actually create this element dynamically in a "manager" element.

    Something like this is how the manager retrieves the data and creates the dialog (I call it a report-grid)...

      newGrid: function(name, useId, useDates, parent) {
        var self = this;
        var body;
        // jshint unused: false
        var dataPromise = new Promise(function(accept, reject) {
          var sendOptions = {
            url: '/api/queries',
            method: 'POST',
            handleAs: 'json',
            headers: {'content-type': 'application/json'}
          };
          body = {};
          body.name = name;
          if (useId) {
            body.id = parent.id;
          }
          if (useDates) {
            body.startdate = parent.startdate;
            body.enddate = parent.enddate;
          }
          sendOptions.body = body;
          var request = document.createElement('iron-request');
          request.send(sendOptions).then(function() {
            accept(request.response);
          });
        });
        // jshint unused: true
        var x;
        var y;
        var grid = document.createElement('pas-report-grid');
        Polymer.dom(self).appendChild(grid);
        if (this.grids.length === 0) {
          x = 0;
          y = 0;
        } else {
          x = this.grids[this.grids.length - 1].x + this.deltaX;
          y = this.grids[this.grids.length - 1].y + this.deltaY;
        }
        this.grids.push(grid);
        grid.open(dataPromise,body,x,y);
    

    And then the element itself has a load of stuff (not shown) to provide drag and resize handles, but the core of the grid is the following templated stuff

      <div class="layout horizontal">
        <template is="dom-repeat" items="[[heading]]">
          <span class="flex">[[item]]</span>
        </template>
      </div>
      <iron-list id="grid" class="flex" items="[[data]]" as="row">
        <template>
          <div class="layout horizontal row" tabindex$="[[tabIndex]]" index="[[index]]">
            <template is="dom-repeat" items="[[row]]" as="field">
              <div class="flex field">[[field]]</div>
            </template>
          </div>
        </template>
      </iron-list>
    

    The open function of the grid does this with the data

      open: function(dataPromise, params, x, y) {
        var self = this;
        this.x = x;
        this.y = y;
        dataPromise.then(function(data) {
          self.title = data.name;
          self.heading = data.heading;
          self.data = data.data;
          self.$.griddialog.open();
        });
        this.params = params;
    

    So what is happening here is the manager is making an iron request (also created dynamically) for a generic query that might or might not need an id and start and end dates, the server responds with a json object which contains a heading array, with a list of heading names, and a data array which is the rows, each row also being an array with the values from the query. I pass that info to the grid element as a promise - so it can get started, attach and so on, and then when the data arrives its loaded into a heading div and an iron list.

    The grid element knows nothing about the actual query, how many fields each row will have, or indeed how many rows.