Skip to content
This repository was archived by the owner on Sep 20, 2020. It is now read-only.

Commit 130e1b4

Browse files
author
christhielen
committed
refactor(sticky): improve clarity in sticky transition calculations
1 parent 990e73e commit 130e1b4

3 files changed

Lines changed: 88 additions & 81 deletions

File tree

src/sticky.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,12 +433,12 @@ angular.module("ct.ui.router.extras.sticky").config(
433433

434434
// We may transition directly to an inactivated state, reactivating it. In this case, we should
435435
// exit all of that state's inactivated children.
436-
var inactiveOrphans = stickyTransitions.inactiveOrphans;
436+
var orphans = stickyTransitions.orphans;
437437
// Add surrogate exited states for all orphaned descendants of the Deepest Reactivated State
438-
surrogateFromPath = surrogateFromPath.concat(map(stickyTransitions.inactiveOrphans, function (exiting) {
438+
surrogateFromPath = surrogateFromPath.concat(map(orphans, function (exiting) {
439439
return stateExitedSurrogate(exiting);
440440
}));
441-
exited = exited.concat(inactiveOrphans);
441+
exited = exited.concat(orphans);
442442

443443
// Replace the .path variables. toState.path and fromState.path are now ready for a sticky transition.
444444
fromState.path = surrogateFromPath;
@@ -502,6 +502,7 @@ angular.module("ct.ui.router.extras.sticky").config(
502502
currentTransition.toState.self.name + ": " +
503503
angular.toJson(currentTransition.toParams);
504504

505+
$log.debug("------------------------------------------------------");
505506
$log.debug(" Current transition: ", transitionMessage);
506507
$log.debug("Before transition, inactives are: : ", map(_StickyState.getInactiveStates(), function (s) {
507508
return s.self.name;

src/stickyProvider.js

Lines changed: 83 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,30 @@ function $StickyStateProvider($stateProvider, uirextras_coreProvider) {
126126
return true;
127127
}
128128

129+
function calcTreeChanges(transition) {
130+
var fromPath = transition.fromState.path;
131+
var toPath = transition.toState.path;
132+
var toParams = transition.toParams;
133+
var keep = 0, state = toPath[keep];
134+
135+
if (transition.options.inherit) {
136+
toParams = inheritParams($stateParams, toParams || {}, $state.$current, transition.toState);
137+
}
138+
139+
while (state && state === fromPath[keep] && paramsEqualForState(state.ownParams, toParams, transition.fromParams)) {
140+
// We're "keeping" this state. bump keep var and get the next state in toPath for the next iteration.
141+
state = toPath[++keep];
142+
}
143+
144+
return {
145+
keep: keep,
146+
retained: fromPath.slice(0, keep),
147+
exiting: fromPath.slice(keep),
148+
entering: toPath.slice(keep)
149+
};
150+
}
151+
152+
129153
var stickySupport = {
130154
getInactiveStates: function () {
131155
return map(inactiveStates, angular.identity);
@@ -139,96 +163,63 @@ function $StickyStateProvider($stateProvider, uirextras_coreProvider) {
139163
// keep: The number of states being "kept"
140164
// inactives: Array of all states which will be inactive if the transition is completed.
141165
// reactivatingStates: Array of all states which will be reactivated if the transition is completed.
142-
// inactiveOrphans: Array of previously inactive states, which are being orphaned by the transition
166+
// orphans: Array of previously inactive states, which are being orphaned by the transition
143167
// Note: Transitioning directly to an inactive state with inactive children will reactivate the state, but exit all the inactive children.
144-
// Note: Each inactiveOrphans is potentially the root of an orphaned subtree. The substates of the orphaned node are not in the list.
145168
// enter: Enter transition type for all added states. This is a parallel array to "toStates" array in $state.transitionTo.
146169
// exit: Exit transition type for all removed states. This is a parallel array to "fromStates" array in $state.transitionTo.
147170
// }
148171
processTransition: function (transition) {
172+
var treeChanges = calcTreeChanges(transition);
173+
var currentInactives = map(inactiveStates, angular.identity);
174+
var futureInactives, exitingTypes, enteringTypes;
175+
var keep = treeChanges.keep;
176+
177+
149178
/////////////////////////////////////////
150179
// helper functions
180+
function notIn(array) { return function (elem) { return array.indexOf(elem) === -1; }; }
151181
function flattenReduce(memo, list) { return memo.concat(list); }
152-
function uniqReduce(memo, orphan) { if (memo.indexOf(orphan) === -1) memo.push(orphan); return memo; }
153-
function pushAll(dest, src) { dest.push.apply(dest, src); return dest; }
154-
function val(x) { return function() { return x; }; }
182+
function uniqReduce(memo, orphan) { if (notIn(memo)(orphan)) memo.push(orphan); return memo; }
155183
function prop(attr) { return function(obj) { return obj[attr]; } }
156-
157184
function typeIs(type) { return function(obj) { return obj.type === type; } }
158185
function isChildOf(state) { return function(other) { return other.parent === state; }; }
159-
function notEntered(state) { return enteringStates.indexOf(state) === -1; }
186+
var notEntering = notIn(treeChanges.entering);
160187
function notSticky(state) { return !state.sticky; }
161188
////////////////////////////////////
162189

163190

164-
// This object is returned
165-
var result = { inactives: [], enter: [], exit: [], keep: 0 };
166-
var fromPath = transition.fromState.path;
167-
var toPath = transition.toState.path;
168-
var toParams = transition.toParams;
169-
var keep = 0, state = toPath[keep];
170-
171-
if (transition.options.inherit) {
172-
toParams = inheritParams($stateParams, toParams || {}, $state.$current, transition.toState);
173-
}
174-
175-
while (state && state === fromPath[keep] && paramsEqualForState(state.ownParams, toParams, transition.fromParams)) {
176-
// We're "keeping" this state. bump keep var and get the next state in toPath for the next iteration.
177-
state = toPath[++keep];
178-
}
179-
180-
result.keep = keep;
181-
var treeChanges = {
182-
retained: fromPath.slice(0, keep),
183-
exiting: fromPath.slice(keep),
184-
entering: toPath.slice(keep)
185-
};
186-
187-
result.exit = treeChanges.retained.map(val(undefined));
188-
result.enter = treeChanges.retained.map(val(undefined));
189-
190-
var inactives = [], reactivatingStates = [], enteringStates = [], exitingStates = [];
191-
var allInactivesByParents = mapInactivesByImmediateParent();
192-
193-
// Two things must be satisfied in order to inactivate "exiting" states (instead of exit them):
191+
// Calculate the "exit" transition types for states being exited in fromPath
192+
// Exit types will be either "inactivate" or "exit"
193+
// Two things must be satisfied in order to inactivate the "exiting" states (instead of exit them):
194194
// - The first element of the exiting path must be sticky
195195
// - We must be entering any sibling state of the sticky (we can check this using entering.length)
196-
//if (treeChanges.exiting[0] && treeChanges.exiting[0].sticky && treeChanges.entering.length > 0) {
197-
var isFromSticky = treeChanges.exiting[0] && treeChanges.exiting[0].sticky && treeChanges.entering.length > 0;
198-
199-
var exiting = treeChanges.exiting.map(function (state) {
200-
var type = isFromSticky ? "inactivate" : "exit";
196+
var shouldInactivate = treeChanges.exiting[0] && treeChanges.exiting[0].sticky && treeChanges.entering.length > 0;
197+
exitingTypes = treeChanges.exiting.map(function (state) {
198+
var type = shouldInactivate ? "inactivate" : "exit";
201199
return { type: type, state: state };
202200
});
203201

204-
pushAll(inactives, exiting.filter(typeIs("inactivate")).map(prop("state")));
205-
pushAll(exitingStates, exiting.filter(typeIs("exit")).map(prop("state")));
206-
pushAll(result.exit, exiting.map(prop("type")));
207202

208-
// Calculate the "enter" transitions for new states in toPath
209-
// Enter transitions will be either "enter", "reactivate", or "reload" where
203+
// Calculate the "enter" transition types for states being entered in toPath
204+
// Enter types will be either "enter", "reactivate", or "reload" where:
210205
// enter: full resolve, no special logic
211206
// reactivate: use previous locals
212207
// reload: like 'enter', except exit the inactive state before entering it.
213208
var reloaded = !!transition.options.reload;
214-
var entering = treeChanges.entering.map(function(state) {
215-
var type = getEnterTransition(state, toParams, transition.reloadStateTree, reloaded);
209+
enteringTypes = treeChanges.entering.map(function(state) {
210+
var type = getEnterTransition(state, transition.toParams, transition.reloadStateTree, reloaded);
216211
reloaded = reloaded || type === 'reload';
217212
return { type: type, state: state };
218213
});
219214

220-
pushAll(enteringStates, entering.map(prop("state")));
221-
pushAll(reactivatingStates, entering.filter(typeIs("reactivate")).map(prop("state")));
222-
pushAll(result.enter, entering.map(prop("type")));
223-
224-
var allInactives = map(inactiveStates, angular.identity);
225-
226215
// Find all the "orphaned" states. those states that are :
216+
// - are siblings of the entering states
227217
// - previously inactive
228-
// - are children or siblings of the entering states
229-
// - are not being reactivated
218+
// - are not being reactivated (entered)
230219
// - are not sticky
231-
// These states should now be exited because of the sticky rules
220+
// unioned with:
221+
// - children of the toState
222+
// - previously inactive
232223
//
233224
// Given:
234225
// - states A (sticky: true), B, A.foo, A.bar
@@ -246,35 +237,50 @@ function $StickyStateProvider($stateProvider, uirextras_coreProvider) {
246237
// Orphan case 3)
247238
// - Transition directly to A orphans the inactive sticky state A.foo; it should be exited
248239
// Note: transition from B to A.bar does not orphan A.foo
249-
var orphanedRoots = enteringStates
250-
// For each entering state in the path, find any sibling states which are currently inactive
251-
.map(function (entering) { return allInactives.filter(isChildOf(entering.parent)); })
240+
// Note 2: each orphaned state might be the parent of a larger inactive subtree.
241+
var orphanedRoots = treeChanges.entering
242+
// For each entering state in the path, find all sibling states which are currently inactive
243+
.map(function (entering) { return currentInactives.filter(isChildOf(entering.parent)); })
252244
// Flatten nested arrays. Now we have an array of inactive states that are children of the ones being entered.
253245
.reduce(flattenReduce, [])
254-
.reduce(uniqReduce, [])
255246
// Consider "orphaned": only those children that are themselves not currently being entered
256-
.filter(notEntered)
247+
.filter(notEntering)
257248
// Consider "orphaned": only those children that are not themselves sticky states.
258-
.filter(notSticky);
259-
var orphanedChildrenOfToState = allInactives.filter(isChildOf(transition.toState));
249+
.filter(notSticky)
250+
// Finally, union that set with any inactive children of the "to state"
251+
.concat(currentInactives.filter(isChildOf(transition.toState)));
252+
253+
var currentInactivesByParent = mapInactivesByImmediateParent();
254+
var allOrphans = orphanedRoots
255+
.map(function(root) { return currentInactivesByParent[root.name] })
256+
.filter(angular.isDefined)
257+
.reduce(flattenReduce, [])
258+
.concat(orphanedRoots)
259+
// Sort by depth to exit orphans in proper order
260+
.sort(function (a,b) { return a.name.split(".").length - b.name.split(".").length; });
260261

261262
// Add them to the list of states being exited.
262-
exitingStates = exitingStates.concat(orphanedRoots).concat(orphanedChildrenOfToState);
263+
var exitOrOrphaned = exitingTypes
264+
.filter(typeIs("exit"))
265+
.map(prop("state"))
266+
.concat(allOrphans);
263267

264268
// Now calculate the states that will be inactive if this transition succeeds.
265269
// We have already pushed the transitionType == "inactivate" states to 'inactives'.
266270
// Second, add all the existing inactive states
267-
inactives = inactives.concat(map(inactiveStates, angular.identity));
268-
// Finally, remove any states that are scheduled for "exit" or "enter", "reactivate", or "reload"
269-
inactives = inactives.filter(function(state) {
270-
return exitingStates.indexOf(state) === -1 && enteringStates.indexOf(state) === -1;
271-
});
272-
273-
result.inactives = inactives;
274-
result.reactivatingStates = reactivatingStates;
275-
result.inactiveOrphans = orphanedRoots.concat(orphanedChildrenOfToState);
276-
277-
return result;
271+
futureInactives = currentInactives
272+
.filter(notIn(exitOrOrphaned))
273+
.filter(notIn(treeChanges.entering))
274+
.concat(exitingTypes.filter(typeIs("inactivate")).map(prop("state")));
275+
276+
return {
277+
keep: keep,
278+
enter: new Array(keep).concat(enteringTypes.map(prop("type"))),
279+
exit: new Array(keep).concat(exitingTypes.map(prop("type"))),
280+
inactives: futureInactives,
281+
reactivatingStates: enteringTypes.filter(typeIs("reactivate")).map(prop("state")),
282+
orphans: allOrphans
283+
};
278284
},
279285

280286
// Adds a state to the inactivated sticky state registry.

test/stickySpec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ describe('stickyState', function () {
546546
});
547547
});
548548

549-
describe("transitions to sibling of inactive state", function() {
549+
describe("transitions to sibling of non-sticky inactive state", function() {
550550
// Tests for issue #217
551551

552552
beforeEach(function() {

0 commit comments

Comments
 (0)