PolymerElements / app-route

A modular client-side router

Home Page:https://www.polymer-project.org/1.0/articles/routing.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Downstream active flag not being reset when route no longer applies

akc42 opened this issue · comments

I have a fairly complex set of routes which I am trying to figure out a way to handle with app route. The more I get into it the more I am finding some limitations. The problem arises from the decision to not alter tail data when the route is no longer selected. I respect that decision, but it has one knock on effect that is causing me problems, namely that the app-route element two down from the element that looses match does not change from active to non-active.

A small subset of my routes look like this:-

/
/appointments
/appointments/:cid/:pid
/appointments/:cir/:pid/day/:date
/reports
/reports/bydate/:section/:startdate/:enddate
/reports/results

My problem stems from the fact that shortened url's generally mean a default page at that level, so for instance in the above list route '/' brings up a menu, '/appointments' with or without the next two parameters (which are optional and I need to default when not supplied) should display a large overview calendar for the current month, which requires that I kick off an ajax request to get that data. I have resorted to using iron-pages "selected-item" property and put an observer on it to watch for changes, but I discovered even that wasn't working when the routes get complex. In order to understand a little bit what was happening I built the following test harness to illustrate the issue (sorry not a jsbin - struggling with funny urls, so I self host - polymer serve will do it, although it doesn't need the fancy component mapping of urls.). What it shows is that routes are remaining active even though they are not. I am working on a pull request to fix the problem - I think the solution is include an active state in the route object - starting with app-location propagating a route with it set to true, and only allowing a route to - but I wanted to raise the issue to go along with other discussions along similar lines.

The code below develops two helper elements <active-display> and <select-indicator> and uses <iron-selector> in place of either <iron-pages> or <paper-menu> to represent how my current application is structured around this limited url set. In my actual code this is all separate nested elements, its flattened in the test just for convenience

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>iron-location repro</title>
  <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
  <link rel="import" href="https://github.com/bower_components/polymer/polymer.html" target="_blank" rel="nofollow">
  <link rel="import" href="/bower_components/iron-flex-layout/iron-flex-layout.html">
  <link rel="import" href="/bower_components/iron-selector/iron-selector.html">
  <link rel="import" href="/bower_components/app-route/app-location.html">
  <link rel="import" href="/bower_components/app-route/app-route.html">
  <link rel="import" href="/bower_components/paper-icon-button/paper-icon-button.html">
  <link rel="import" href="/bower_components/iron-icons/iron-icons.html">
  <style >
    body {
      margin: 0;
    }
    test-elem {
      margin: 20px;
      display: block;
    }

  </style>
</head>
<body>
<dom-module id="active-display">
  <template>
    <style>
      :host {
        display: block;
        margin: 4px;
        @apply(--layout-vertical);
        border:1px solid green;
      };
      .marker {
        width: 10px;
        height: 10px;
        border:2px solid black;
      }
      .active {
        background: red;
      }
      .inactive {
        background: white;
      }

      .indicate {
        background: blue;
      }
    </style>
    <div class$="[[_classify(active)]]"></div>
    <div><content></content></div>
  </template>
  <script>
    Polymer({
      is: 'active-display',
      properties: {
        active: {
          type: Boolean,
          value: false
        },
        indicate: {
          type: Boolean,
          value: false
        }
      },
       _classify: function(active) {
        if (active) {
          if (this.indicate) {
            return 'marker indicate';
          }
          return 'marker active';
        }
        return 'marker inactive';
      }

    });
  </script>
</dom-module>

  <dom-module id="select-indicator">
    <template>
      <style>
        :host {
          display:block;
          margin: 4px;
          @apply(--layout-horizontal);
          border:1px solid blue;
        };
      </style>
      <active-display active="[[active]]" indicate>[[name]]</active-display>
      <div><content></content></div>
    </template>
    <script>
    Polymer({
      is: 'select-indicator',
      properties: {
        selected: {
          type: Object,
          observer: '_selectChanged'
        },
        active: {
          type: Boolean,
          value: false,
          notify:true
        },
        name: {
          type: String,
          value: ''
        }
      },
      _selectChanged(selected) {
        if(selected === this) {
          this.active = true;
        } else {
          this.active = false;
        }
      }
    });
    </script>
</dom-module>


  <dom-module id='test-elem'>
    <template>
      <style>
        : host {
          display: block;
        };
        active-display div.iron-selected {
          background: #eee;
        }
        .container {
          @apply(--layout-horizontal);
        }
        paper-icon-button {
          color: blue;
        }
      </style>
      <app-location route="{{route}}"></app-location>
      <app-route
        route="{{route}}"
        pattern="/:page"
        active="{{routeAtive}}"
        data="{{data}}"
        tail="{{subRoute}}"></app-route>
      <div class="container">
        <active-display active="[[routeActive]]">
          <div>Main Route</div>
          <div> Change Route <paper-icon-button id="change" icon="redo" title="redo" ></paper-icon-button></div>
          <iron-selector selected='[[lastIndex]]'>
            <template is="dom-repeat" items="[[urls]]">
              <div>[[item]]</div>
            </template>
          </iron-selector>
        </active-display>
        <iron-selector
          role="main"
          selected="[[mainPage]]"
          attr-for-selected="name"
          selected-item="{{mainSelect}}">
          <select-indicator
            name="home"
            selected="[[mainSelect]]">
            <div>Main Menu</div>
          </select-indicator>
          <select-indicator
            name="appointments"
            selected="[[mainSelect]]">
            <app-route
              route="{{subRoute}}"
              active="{{cidpidActive}}"
              pattern="/:cid/:pid"
              data="{{cidpidData}}"
              tail="{{cidpidSubRoute}}"></app-route>
            <app-route
              route="{{cidpidSubRoute}}"
              pattern="/:page"
              active="{{apptActive}}"
              data="{{apptData}}"
              tail="{{apptSubRoute}}"></app-route>
            <active-display active="[[cidpidActive]]">Appt Cid=[[apptData.cid]] Pid=[[apptData.pid]] </active-display>
            <active-display active="[[apptActive]]">Appt Month Day</active-display>
            <iron-selector
              selected="[[apptPage]]"
              attr-for-selected="name"
              selected-item="{{apptSelect}}">
              <select-indicator
               id="overview"
               name="month"
               selected="[[apptSelect]]">
                <iron-selector selected="{{clinic}}" attr-for-selected="cid" selected-item="{{clinicSelect}}">
                  <select-indicator id="cid1" cid="1" selected="[[clinicSelect]]">London</select-indicator>
                  <select-indicator id="cid2" cid="2" selected="[[clinicSelect]]">Birmingham</select-indicator>
                  <select-indicator id="cid3" cid="3" selected="[[clinicSelect]]">Leeds</select-indicator>
                </iron-selector>
              </select-indicator>
              <select-indicator
               name="day"
               selected="[[apptSelect]]">
               <app-route
                route="{{apptSubRoute}}"
                active="{{dayActive}}"
                pattern="/:day"
                data="{{dayData}}"></app-route>
               <active-display active="[[dayActive]]">Appt Day=[[dayData.day]]</active-display>
              </select-indicator>
            </iron-selector>
          </select-indicator>
          <select-indicator
            name="reports"
            selected="[[mainSelect]]">
            <app-route
              route="{{subRoute}}"
              active="{{reportActive}}"
              pattern="/:page"
              data="{{reportData}}"
              tail="{{reportSubRoute}}"></app-route>
            <active-display active="[[reportActive]]">Reports</active-display>
            <iron-selector
              selected="[[reportPage]]"
              attr-for-selected="name"
              selected-item="{{reportSelect}}">
              <select-indicator
                name="home"
                selected="[[reportSelect]]">
                <div>Report Menu</div>
              </select-indicator>
              <select-indicator
                name="bydate"
                selected="[[reportSelect]]">
               <app-route
                route="{{reportSubRoute}}"
                active="{{dateActive}}"
                pattern="/:date"
                data="{{dateData}}"></app-route>
               <active-display active="[[dateActive]]">Report Date=[[dateData.date]]</active-display>
              </select-indicator>
              <select-indicator
                name="results"
                selected="[[reportSelect]]"></select-indicator>
            </iron-selector>
          </select-indicator>
        </iron-selector>
      </div>
    </template>
  </dom-module>
  <script>
    document.addEventListener('WebComponentsReady', function() {
      Polymer({
        is: 'test-elem',
        properties: {
          route: {
            type: Object
          },
          subRoute: {
            type: Object
          },
          cidpidSubRoute: {
            type: Object
          },
          apptSubRoute: {
            type: Object
          },
          reportSubRoute: {
            type: Object
          },
          routeActive: {
            type: Boolean,
            value: false
          },
          cidpidctive: {
            type: Boolean,
            value: false
          },
          apptActive: {
            type: Boolean,
            value: false
          },
          dayActive: {
            type: Boolean,
            value: false
          },
          reportActive: {
            type: Boolean,
            value: false
          },
          dayActive: {
            type: Boolean,
            value: false
          },
          data: {
            type: Object
          },
          cidpidData: {
            type: Object
          },
          apptData: {
            type: Object
          },
          dayData: {
            type: Object
          },
          reportData: {
            type: Object
          },
          dateData: {
            type: Object
          },
          mainPage: {
            type: String,
            value: '',
            readOnly: true
          },
          apptPage: {
            type: String,
            value: '',
            readOnly: true
          },
          reportPage: {
            type: String,
            value: '',
            readOnly: true
          },
          clinic: {
            type: Number
          },
          mainSelect: {
            type: Object,
            value: function(){return {};}
          },
          apptSelect: {
            type: Object,
            value: function(){return {};},
            oberver: '_apptSelectChanged'
          },
          clinicSelect: {
            type: Object,
            value: function(){return {};}
          },
          reportSelect: {
            type: Object,
            value: function(){return {};}
          },
          lastIndex: {
            type: Number,
            value: 0
          },
          urls: {
            type: Array,
            value: function() {
              return [
                '/',
                '/appointments',
                '/appointments/2/3',
                '/appointments/2/3/day/20161001',
                '/reports',
                '/reports/results',
                '/reports',
                '/reports/bydate/20150101',
                '/appointments'
              ];
            }
          }
        },
        observers: [
        '_dataPageChanged(data.page)',
        '_apptDataPageChanged(apptData.page)',
        '_reportDataPageChanged(reportData.page)',
        '_routePathChanged(route.path)',
        '_cidpidSubRoutePathChanged(cidpidSubRoute.path)',
        '_subRoutePathChanged(subRoute.path)'
        ],
        listeners: {
          'change.tap': '_changeURL'
        },
        attached: function() {
          this.originalLocation = window.location.href;
          this.switchPath(this.urls[this.lastIndex]);
        },
        detached: function() {
            window.history.replaceState({}, null, this.originalLocation);
        },
        switchPath: function(path) {
          window.history.replaceState({}, null, path);
          window.dispatchEvent(new CustomEvent('location-changed'));
        },
        _dataPageChanged: function(page) {
          this._setMainPage(page || 'home');
        },
        _apptDataPageChanged: function(page) {
          this._setApptPage(page || 'month');
        },
        _reportDataPageChanged: function(page) {
          this._setReportPage(page || 'home');
        },
        _routePathChanged: function(path) {
          if (path === '') {
            this._setMainPage('home');
          }
        },
        _cidpidSubRoutePathChanged: function(path) {
          if(path === '') {
            this._setApptPage('month');
          }
        },
        _subRoutePathChanged: function(path) {
          if(path === '') {
            this._setReportPage('home');
          }
        },
        _apptSelectChanged: function(selected) {
          if(selected = this.$.overview) {
            if (this.cidpidData.cid === undefined) {
              this.clinic = 1;
            } else {
              this.clinic = this.cidpidData.cid;
            }

          }
        },
        _changeURL: function() {

          this.lastIndex++;
          if(this.lastIndex >= this.urls.length) {
            this.lastIndex = 0;
          }
          this.switchPath(this.urls[this.lastIndex]);
        }
      });
    });
  </script>
  <test-elem></test-elem>


</body>
</html>



I think this is a duplicate of #110 which is fixed in #125

@TimvdLippe I think it might be. I'm out today, so haven't got much time, but I'll take a look at the pull request when I'm back to see if it solves my issue.

I am now more confused about whether I am asking the right question. I ran the fix in #125 against my test harness and its still not working. However I now realize why.. My top level <app-route> has a pattern of /:page I feed the tail for this into several <app-route> elements representing different parts of the app (in the test harness they are the options of an <iron-selector>, driven from what matches that '/:page'). One leg goes 'appointments' and to an <app-route> element with a pattern of /:cid/:pid. another leg ('reports') goes an <app-route> with a '/:page' patten to effectively control another <iron-selector>. The bydate' leg of this then has a further` element with a pattern of '/:date'

So looking at the chains of <app-route> patterns

/:page/:cid/:pid
and
/:page/:page/:date

BOTH of those are active and match /appointments/2/3 and /reports/bydate/2015010

So I am left thinking about when should I just propagate a route down the tree rather than the subroute, so that I can for instance do /:page at the top level and then a pattern which includes the previous segment at the next level - so /appointments/:cid/:pid and /reports/:page. The problem comes when I don't know the previous segments because they aren't just an a particular choice on an <iron-selector> (or in the real app an <iron-pages>).

I have done some more work on fixing the issue raised in my previous comment, and although #125 fixes some of the problem there is still an issue with the attempt to set multiple properties at the same time.

Firstly - the patterns issue I raised in the last comment. The solution to this problem is to break up the chains, restarting them one step back when its a page lookup. So my pattern chains now look like

|- /:page
|-/appointments -  /:cid/:pid - /day - /:day
|-/reports -|-  /:page
            | - /bydate - /:date

The whole test harness still doesn't work, but that is because of a problem related to the way multiple properties are set at the same time. I will raise this in a new issue, and close this one now