@@ -13,6 +13,7 @@ class Router extends Backbone.Router {
13
13
return {
14
14
'' : 'handleRoute' ,
15
15
'id/:id' : 'handleRoute' ,
16
+ 'preview/:id' : 'handlePreview' ,
16
17
':pluginName(/*location)(/*action)' : 'handleRoute'
17
18
} ;
18
19
}
@@ -78,6 +79,50 @@ class Router extends Backbone.Router {
78
79
this . handleRoute ( ...args ) ;
79
80
}
80
81
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
+
81
126
handleRoute ( ...args ) {
82
127
args = args . filter ( v => v !== null ) ;
83
128
@@ -175,6 +220,7 @@ class Router extends Backbone.Router {
175
220
// Move to a content object
176
221
this . showLoading ( ) ;
177
222
await Adapt . remove ( ) ;
223
+ await this . removePreviews ( ) ;
178
224
179
225
/**
180
226
* TODO:
@@ -222,6 +268,127 @@ class Router extends Backbone.Router {
222
268
223
269
}
224
270
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
+
225
392
async updateLocation ( currentLocation , type , id , currentModel ) {
226
393
// Handles updating the location.
227
394
location . _previousModel = location . _currentModel ;
0 commit comments