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>
@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