Skip to content

Commit 349629b

Browse files
authored
Fix: Allow /#/preview/b-05 for rendering a single id (fixes #488) (#495)
1 parent 500b9c4 commit 349629b

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

js/router.js

+167
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Router extends Backbone.Router {
1313
return {
1414
'': 'handleRoute',
1515
'id/:id': 'handleRoute',
16+
'preview/:id': 'handlePreview',
1617
':pluginName(/*location)(/*action)': 'handleRoute'
1718
};
1819
}
@@ -78,6 +79,50 @@ class Router extends Backbone.Router {
7879
this.handleRoute(...args);
7980
}
8081

82+
handlePreview(...args) {
83+
args = args.filter(v => v !== null);
84+
85+
if (this.model.get('_canNavigate')) {
86+
// Reset _isCircularNavigationInProgress protection as code is allowed to navigate away.
87+
this._isCircularNavigationInProgress = false;
88+
}
89+
90+
// Check if the current page is in the process of navigating to itself.
91+
// It will redirect to itself if the URL was changed and _canNavigate is false.
92+
if (this._isCircularNavigationInProgress === false) {
93+
// Trigger an event pre 'router:location' to allow extensions to stop routing.
94+
Adapt.trigger('router:navigate', args);
95+
}
96+
97+
// Re-check as _canNavigate can be set to false on 'router:navigate' event.
98+
if (this.model.get('_canNavigate')) {
99+
// Disable navigation whilst rendering.
100+
this.model.set('_canNavigate', false, { pluginName: 'adapt' });
101+
this._isBackward = false;
102+
if (args.length <= 1) {
103+
return this.handleIdPreview(...args);
104+
}
105+
return this.handlePluginRouter(...args);
106+
}
107+
108+
if (this._isCircularNavigationInProgress) {
109+
// Navigation correction finished.
110+
// Router has successfully re-navigated to the current _id as the URL was changed
111+
// while _canNavigate: false
112+
this._isCircularNavigationInProgress = false;
113+
return;
114+
}
115+
116+
// Cancel navigation to stay at the current location.
117+
this._isCircularNavigationInProgress = true;
118+
Adapt.trigger('router:navigationCancelled', args);
119+
120+
// Reset URL to the current one.
121+
// https://github.com/adaptlearning/adapt_framework/issues/3061
122+
Backbone.history.history[this._isBackward ? 'forward' : 'back']();
123+
this._isBackward = false;
124+
}
125+
81126
handleRoute(...args) {
82127
args = args.filter(v => v !== null);
83128

@@ -175,6 +220,7 @@ class Router extends Backbone.Router {
175220
// Move to a content object
176221
this.showLoading();
177222
await Adapt.remove();
223+
await this.removePreviews();
178224

179225
/**
180226
* TODO:
@@ -222,6 +268,127 @@ class Router extends Backbone.Router {
222268

223269
}
224270

271+
async handleIdPreview(id) {
272+
const rootModel = router.rootModel;
273+
let model = (!id) ? rootModel : data.findById(id);
274+
275+
if (!model) {
276+
// Bad id
277+
this.model.set('_canNavigate', true, { pluginName: 'adapt' });
278+
return;
279+
}
280+
281+
// Move to a content object
282+
this.showLoading();
283+
await Adapt.remove();
284+
await this.removePreviews();
285+
286+
let isContentObject = model.isTypeGroup?.('contentobject');
287+
if (!isContentObject) {
288+
// If the preview id is not a content object then make
289+
// some containers to put it in
290+
const types = ['page', 'article', 'block', 'component'];
291+
const type = model.get('_type');
292+
const buildTypes = types.slice(0, types.indexOf(type));
293+
let parentModel = Adapt.course;
294+
let _parentId = parentModel.get('_id');
295+
const built = buildTypes.map((_type, index) => {
296+
const ModelClass = components.getModelClass({ _type });
297+
const _id = `preview-${_type}`
298+
const builtModel = new ModelClass({
299+
_type,
300+
_id,
301+
_parentId,
302+
_isPreview: true
303+
});
304+
if (index) parentModel.getChildren().add(builtModel)
305+
data.add(builtModel)
306+
parentModel = builtModel
307+
_parentId = _id
308+
return builtModel
309+
})
310+
// Clone the requested content to sanitise
311+
model.deepClone((clone, orig) => {
312+
// Make the cloned item available and unlocked
313+
clone.set({
314+
_isAvailable: true,
315+
_isLocked: false
316+
});
317+
clone.on('change', function () {
318+
// Sync the cloned item with the original
319+
const state = this.getAttemptObject
320+
? this.getAttemptObject()
321+
: this.getTrackableState();
322+
delete state._id;
323+
delete state._isAvailable;
324+
delete state._isLocked;
325+
if (this.getAttemptObject) orig.setAttemptObject(state);
326+
else orig.set(state);
327+
});
328+
if (orig !== model) return;
329+
// Relocate the cloned item into the preview containers
330+
clone.set({
331+
_parentId
332+
});
333+
});
334+
built.forEach(model => model.setupModel());
335+
isContentObject = true
336+
model = built[0]
337+
model.setOnChildren({ _isPreview : true })
338+
}
339+
340+
const navigateToId = model.get('_id');
341+
342+
// Ensure that the router is rendering a contentobject
343+
model = isContentObject ? model : model.findAncestor('contentobject');
344+
id = model.get('_id');
345+
346+
/**
347+
* TODO:
348+
* As the course object has separate location and type rules,
349+
* it makes it more difficult to update the location object
350+
* should stop doing this.
351+
*/
352+
const isCourse = model.isTypeGroup?.('course');
353+
const type = isCourse ? 'menu' : model.get('_type');
354+
const newLocation = isCourse ? 'course' : `${type}-${id}`;
355+
356+
model.set({
357+
_isVisited: true,
358+
_isRendered: true
359+
});
360+
await this.updateLocation(newLocation, type, id, model);
361+
362+
Adapt.once('contentObjectView:ready', () => {
363+
// Allow navigation.
364+
this.model.set('_canNavigate', true, { pluginName: 'adapt' });
365+
this.handleNavigationFocus();
366+
});
367+
Adapt.trigger(`router:${type} router:contentObject`, model);
368+
369+
const ViewClass = components.getViewClass(model);
370+
const isMenu = model.isTypeGroup?.('menu');
371+
if (!ViewClass && isMenu) {
372+
logging.deprecated(`Using event based menu view instantiation for '${components.getViewName(model)}'`);
373+
return;
374+
}
375+
376+
if (!isMenu) {
377+
// checkIfResetOnRevisit where exists on descendant models before render
378+
_.invoke(model.getAllDescendantModels(), 'checkIfResetOnRevisit');
379+
// wait for completion to settle
380+
await Adapt.deferUntilCompletionChecked();
381+
}
382+
383+
this.$wrapper.append(new ViewClass({ model }).$el);
384+
385+
}
386+
387+
async removePreviews() {
388+
const previews = data.filter(model => model.get('_isPreview'))
389+
previews.forEach(model => data.remove(model));
390+
}
391+
225392
async updateLocation(currentLocation, type, id, currentModel) {
226393
// Handles updating the location.
227394
location._previousModel = location._currentModel;

0 commit comments

Comments
 (0)