Adding Custom Web Assets for FullCalendar (Symfony / EasyAdmin) - symfony

Symfony : 6.1.4
EasyAdmin : 4.3.5
According to the documentation for adding Custom Web Assets :
Use the configureAssets() method in the dashboard and/or the CRUD controllers to add your own CSS and JavaScript files
So I want to add the FullCalendar assets.
class DashboardController extends AbstractDashboardController
{
//...
public function configureAssets(): Assets
{
return parent::configureAssets()
->addWebpackEncoreEntry('admin')
->addCssFile('https://cdn.jsdelivr.net/npm/fullcalendar#5.11.3/main.min.css')
->addJsFile('https://cdn.jsdelivr.net/npm/fullcalendar#5.11.3/main.min.js');
}
//...
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/index.html.twig');
}
}
admin/index.html.twig :
// ...
{% block main %}
<div id="booking"></div>
{% endblock %}
{% block javascripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('booking');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
locale: 'fr',
buttonText: {
today: 'Aujourd\'hui',
month: 'Mois',
week: 'Semaine'
},
timeZone: 'Europe/Paris',
headerToolbar: {
start: 'prev,next today',
center: 'title',
end: 'dayGridMonth,timeGridWeek'
},
events: {{ data|raw }},
navLinks: true
});
calendar.render();
});
</script>
{% endblock %}
But the calendar is not displayed.
No errors in my browser console.

Related

xhr.send is not sending data in symfony how do i fix it?

I'm having a problem with xhr.send(JSON.stringify(donnees)) , im trying to edit with calendar but it's not sending data.
here is my code.
this is the twig part :`
{% extends 'base-back.html.twig' %}
{% block body %}
<br>
{% block title %}<center><h1>Vols Calendar</h1></center>{% endblock %}
<br><br><br>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar#5.6.0/main.min.js" integrity="sha256-ekrJn2FeZaiUFq99QswpQCUTME/HxaDfX7R8INzKJGE=" crossorigin="anonymous"></script>
<div id="calendrier">
<script src="{{ asset('assets/js/fullcalendar.js') }}"></script>
</div>
{% block stylesheet %}
<style>
#calendrier{
width: 600px;
margin: auto;
height:60%;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar#5.6.0/main.min.css" integrity="sha256-uq9PNlMzB+1h01Ij9cx7zeE2OR2pLAfRw3uUUOOPKdA=" crossorigin="anonymous">
{% endblock %}
{% block javascript %}
<script>
window.onload = () =>{
let calendarElt=document.querySelector("#calendrier");
let calendar =new FullCalendar.Calendar(calendarElt,{
initialView: 'dayGridMonth',
locale: 'fr',
timeZone: 'Afrique/Tunisie',
headerToolbar: {
start: 'prev,next today',
center: 'title',
end: 'dayGridMonth'
},
events: {{data|raw}},
//can edit
editable: true,
//can make it 2 days for example
eventResizableFromStart: true
});
//get an object when you move an event
calendar.on('eventChange', (e)=>{
let url =`/vols/api/${e.event.id}/edit`
let donnees={
"destination_aller":e.event.extendedProps.destination_aller,
"destination_retour":e.event.extendedProps.destination_retour,
"title":e.event.title,
"start":e.event.start,
"date_retour":e.event.extendedProps.date_retour,
"passagers":e.event.extendedProps.passagers,
"cabine":e.event.extendedProps.cabine,
}
console.log(donnees);
let xhr = new XMLHttpRequest()
xhr.open("PUT", url)
xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");
xhr.send(JSON.stringify(donnees))
})
calendar.render();
}
</script>
{% endblock %}{% endblock %}
and this is my controller :
/**
* #Route("/calendar", name="calendar")
*/
public function calendar()
{
$repository = $this->getDoctrine()->getRepository(vols::class);
$event = $repository->findAll();
$rdvs=[];
foreach($event as $event){
$rdvs[]=[
'id' => $event->getId(),
'destination_aller' => $event->getDestinationAller(),
'destination_retour'=>$event->getDestinationRetour(),
'title' => $event->getVoyage(),
'start' => $event->getDateDepart()->format('Y-m-d'),
'date_retour' => $event->getDateRetour(),
'passagers' => $event-> getPassagers(),
'cabine'=>$event->getCabine(),
];
}
$data= json_encode($rdvs);
return $this->render('vols/Callender.html.twig', compact('data'));
}
/**
* #Route("/api/{id}/edit", name="editCalendar", methods={"PUT"})
*/
public function editCalendar(vols $calendar, Request $request) {
//récuperation de données
$donnees=json_decode($request->getContent());
if(
isset($donnees->destination_aller) && !empty($donnees->destination_aller) &&
isset($donnees->destination_retour) && !empty($donnees->destination_retour) &&
isset($donnees->title) && !empty($donnees->title) &&
isset($donnees->start) && !empty($donnees->start) &&
isset($donnees->date_retour) && !empty($donnees->date_retour) &&
isset($donnees->passagers) && !empty($donnees->passagers) &&
isset($donnees->cabine) && !empty($donnees->cabine)
){
//données completes, on initialise un code
$code = 200;
//verification de l'id existe
if(!$calendar){
//on instancie un rende
$calendar = new vols;
//change code
$code=201;
}
//on hydrate l'objet avec les données
$calendar->setDestinationAller($donnees->destination_aller);
$calendar->setDestinationRetour($donnees->destination_retour);
$calendar->setVoyage($donnees->title);
$calendar->setDateDepart(new DateTime($donnees->start));
$calendar->setDateRetour(new DateTime($donnees->date_retour));
$calendar->setPassagers($donnees->passagers);
$calendar->setCabine($donnees->cabine);
$em=$this->getDoctrine()->getManager();
$em->persist($calendar);
$em->flush();
//return code
return new Response('Ok', $code);
}else{
//données non completes
return new Response('data not complete', 404);
}
return $this->render('vols/calenderEdit.html.twig');
}
my data is sent but i can't edit it
thank you.

Fullcalendar cannot read property destroy of undefined when going directly to the page

I am using Nuxt 2.14.3 and I want to use the fullcalendar library. For this I have added the following packages via Yarn:
yarn add #fullcalendar/vue
yarn add #fullcalendar/interaction
yarn add #fullcalendar/daygrid
yarn add #fullcalendar/timegrid
yarn add #fullcalendar/list
My Vue template file looks like this:
<template>
<client-only placeholder="loading...">
<FullCalendar class="demo-app-calendar" :options="calendarOptions">
<template #eventContent="arg">
<b>{{ arg.timeText }}</b>
<i>{{ arg.event.title }}</i>
</template>
</FullCalendar>
</client-only>
</template>
<script>
import FullCalendar from '#fullcalendar/vue'
import dayGridPlugin from '#fullcalendar/daygrid'
import timeGridPlugin from '#fullcalendar/timegrid'
import interactionPlugin from '#fullcalendar/interaction'
export default {
components: {
FullCalendar,
},
mixins: [
page({
meta() {
return {
title: this.$tU('meta_title_vrm_planning'),
}
},
}),
],
data() {
return {
calendarOptions: {
plugins: [
dayGridPlugin,
timeGridPlugin,
interactionPlugin, // needed for dateClick
],
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay',
},
initialView: 'dayGridMonth',
initialEvents: [
{
id: 1,
title: 'All-day event',
start: new Date().toISOString().replace(/T.*$/, ''),
},
{
id: 2,
title: 'Timed event',
start: `${new Date().toISOString().replace(/T.*$/, '')}T12:00:00`,
},
], // alternatively, use the `events` setting to fetch from a feed
editable: true,
selectable: true,
selectMirror: true,
dayMaxEvents: true,
weekends: true,
select: this.handleDateSelect,
eventClick: this.handleEventClick,
eventsSet: this.handleEvents,
/* you can update a remote database when these fire:
eventAdd:
eventChange:
eventRemove:
*/
},
currentEvents: [],
}
},
methods: {
handleDateSelect(selectInfo) {
const title = prompt('Please enter a new title for your event')
const calendarApi = selectInfo.view.calendar
calendarApi.unselect() // clear date selection
if (title) {
calendarApi.addEvent({
id: 3,
title,
start: selectInfo.startStr,
end: selectInfo.endStr,
allDay: selectInfo.allDay,
})
}
},
handleEventClick(clickInfo) {
if (confirm(`Are you sure you want to delete the event '${clickInfo.event.title}'`)) {
clickInfo.event.remove()
}
},
handleEvents(events) {
this.currentEvents = events
},
},
}
</script>
It gives me the following errors:
[Vue warn]: Error in mounted hook: "TypeError: DateProfileGeneratorClass is not a constructor"
found in
---> <FullCalendar>
and
TypeError: DateProfileGeneratorClass is not a constructor
at buildDateProfileGenerator (main.js?d610:7232)
and also
TypeError: Cannot read property 'destroy' of undefined
at VueComponent.beforeDestroy (FullCalendar.js?ff68:33)
Note: this works perfectly when accessing my page from another page, but when going directly to the page URL (or when clicking CTRL+F5) I receive the errors as mentioned above.
Note 2: I tried transpiling the library in Nuxt config as suggested in the example repo https://github.com/fullcalendar/fullcalendar-example-projects/tree/master/nuxt, but it gives me the exact same errors, but with the .ts files instead of the .js files.

Using ElFinder on Symfony: I can't select images

I want to use ElFinder as my file manager in my Symfony project. I followed the doc on GitHub. In my routes.yaml:
elfinder:
resource: '#FMElfinderBundle/Resources/config/routing.yaml'
security.yaml
- { path: ^/efconnect, role: [ROLE_USER] }
- { path: ^/elfinder, role: [ROLE_USER] }
And finally on fm_elfinder.yaml
fm_elfinder:
instances:
default:
locale: '%locale%' # defaults to current request locale
editor: ckeditor # other options are tinymce, tinymce4, fm_tinymce, form, simple, custom
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads
upload_max_size: 2M
Then I added it on a Admin element on Sonata, like this:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu')
->add('published', CheckboxType::class, ['required' => false, 'label' => 'Publier'])
->add('title', TextType::class, ['required' => true, 'label' => 'Titre'])
->add('textLink', TextType::class, ['required' => true, 'label' => 'Texte du lien'])
->add('media', ElFinderType::class, array(
'label' => 'Photo',
'enable' => true,
'required' => true,
'instance' => 'default',
'attr' => array('class' => 'form-control')
)
)
->end();
}
Then I go on Sonata Admin, and when I try to add an image, a window open, I added an jpeg but then when i click on it, nothing seems to happen. Like, I can select it but my window stay open and my field doesn't fill up with the name of the image.
Thanks for your help.
Ok I got it to work on my easyadmin forms. This is the easyAdmin 2 configuration :
easy_admin:
entities:
BlogPost:
class: App\Entity\BlogPost
form:
fields:
- { property: 'title', label: 'page.title' }
- { property: 'image', type: 'App\Form\ElFinderType', label: 'blog.image.file', type_options: { enable: true, instance: 'single' } }
(I copied the vendor/helios-ag/fm-elfinder-bundle/src/Form/Type/ElFinderType.php file to App\Form\ElFinderType.php. This is not required).
I set the fm_elfinder connector to simple :
# src/packages/fm_elfinder.yaml
fm_elfinder:
instances:
single:
locale: fr
editor: simple
theme: smoothness
relative_path: false
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads/images
show_hidden: false
upload_allow: [ 'image/png', 'image/jpg', 'image/jpeg' ]
upload_deny: [ 'all' ]
upload_max_size: 2M
Next I copied two of the template files from the bundle to my templates\bundles directory :
{# templates/bundles/FMElfinderBundle/Form/elfinder_widget.html.twig #}
{% block elfinder_widget %}
<div class="buttons">
<input type="button" {{ block('widget_attributes') }}
data-type="elfinder-remove-button" value="Delete" class="{% if value is empty %}hide{% endif %} remove-button"/>
<input type="button" {{ block('widget_attributes') }}
data-type="elfinder-add-button" value="Add" class="{% if value is not empty %}hide{% endif %} add-button"/>
</div>
{% if value is not empty %}
<img src="{{ value }}" alt="">
{% endif %}
<input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}
data-type="elfinder-input-field" class="hide"/>
{% if enable and instance is defined %}
<script type="text/javascript" charset="utf-8">
const addButtonSelector = '[data-type="elfinder-add-button"][id="{{ id }}"]';
const removeButtonSelector = '[data-type="elfinder-remove-button"][id="{{ id }}"]';
const inputSelector = '[data-type="elfinder-input-field"][id="{{ id }}"]';
live('click', addButtonSelector, () => {
window.open("{{ path('elfinder', {'instance': instance, 'homeFolder': homeFolder }) }}?id={{ id }}", "popupWindow", "height=450, width=900");
});
removeButtonListener()
function live(eventType, elementQuerySelector, cb) {
document.addEventListener(eventType, function (event) {
const qs = document.querySelectorAll(elementQuerySelector);
if (qs) {
let el = event.target, index = -1;
while (el && ((index = Array.prototype.indexOf.call(qs, el)) === -1)) {
el = el.parentElement;
}
if (index > -1) {
cb.call(el, event);
}
}
})
}
function setValue(value) {
document.querySelector(inputSelector).value = value;
}
function displayImage(value) {
const inputElement = document.querySelector(inputSelector);
const parent = inputElement.parentElement
const image = document.createElement("img");
image.src = value;
parent.append(image)
const removeButton = document.querySelector(removeButtonSelector);
const addButton = document.querySelector(addButtonSelector);
removeButton.classList.remove('hide')
addButton.classList.add('hide')
removeButtonListener()
}
function removeButtonListener() {
const removeButtonElement = document.querySelector(removeButtonSelector);
if(removeButtonElement){
removeButtonElement.addEventListener('click', () => {
const addButtonElement = document.querySelector(addButtonSelector);
removeButtonElement.classList.remove('hide')
const parent = removeButtonElement.closest('.form-widget')
const inputElement = parent.querySelector('[data-type="elfinder-input-field"]');
inputElement.value = null
const imageElement = parent.getElementsByTagName('img')[0]
if (imageElement) {
parent.removeChild(imageElement)
}
removeButtonElement.classList.add('hide')
addButtonElement.classList.remove('hide')
document.querySelector(inputSelector).value = null;
})
}
}
</script>
{% endif %}
{% endblock %}
This first one contains the fields and the JavaScript needed to fill them in. I added a Add button. This will open the elfinder, once an image gets selected this button becomes hidden and a Remove button will be added.
Next is the ElFinder window :
{# templates/bundles/FMElfinderBundle/Elfinder/simple.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<script data-main="{{ path('ef_main_js') }}"
src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
function getUrlParam(paramName) {
const reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i');
const match = window.location.search.match(reParam);
return (match && match.length > 1) ? match[1] : '' ;
}
define('elFinderConfig', {
// elFinder options (REQUIRED)
// Documentation for client options:
// https://github.com/Studio-42/elFinder/wiki/Client-configuration-options
defaultOpts: {
url: '{{ path('ef_connect', { 'instance': instance, 'homeFolder': homeFolder } ) }}',
lang: '{{ locale }}',
onlyMimes: {{ onlyMimes|raw }},
getFileCallback: function (file) {
window.opener.setValue(file.url);
window.opener.displayImage(file.url);
window.close();
},
commandsOptions: {
edit: {
extraOptions: {
// set API key to enable Creative Cloud image editor
// see https://console.adobe.io/
creativeCloudApiKey: '',
// browsing manager URL for CKEditor, TinyMCE
// uses self location with the empty value
managerUrl: ''
}
},
quicklook: {
// to enable CAD-Files and 3D-Models preview with sharecad.org
sharecadMimes: ['image/vnd.dwg', 'image/vnd.dxf', 'model/vnd.dwf', 'application/vnd.hp-hpgl', 'application/plt', 'application/step', 'model/iges', 'application/vnd.ms-pki.stl', 'application/sat', 'image/cgm', 'application/x-msmetafile'],
// to enable preview with Google Docs Viewer
googleDocsMimes: ['application/pdf', 'image/tiff', 'application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/postscript', 'application/rtf'],
// to enable preview with Microsoft Office Online Viewer
// these MIME types override "googleDocsMimes"
officeOnlineMimes: ['application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation']
}
},
// bootCallback calls at before elFinder boot up
bootCallback: function (fm, extraObj) {
/* any bind functions etc. */
fm.bind('init', function () {
});
// for example set document.title dynamically.
var title = document.title;
fm.bind('open', function () {
var path = '',
cwd = fm.cwd();
if (cwd) {
path = fm.path(cwd.hash) || null;
}
document.title = path ? path + ':' + title : title;
}).bind('destroy', function () {
document.title = title;
});
}
},
managers: {
// 'DOM Element ID': { /* elFinder options of this DOM Element */ }
'elfinder': {}
}
});
</script>
</head>
<body>
<!-- Element where elFinder will be created (REQUIRED) -->
<div id="elfinder"></div>
</body>
</html>
The main thing added here to make it work is the getFileCallback function :
getFileCallback: function (file) {
window.opener.setValue(file.url);
window.opener.displayImage(file.url);
window.close();
},
This now works fine for my single image field and does not affect the CKEditor integration.

How to add listeners for properties in an auto-binding template in Polymer?

I'm writing a webapp with Polymer and I'm using an auto-binding template and a property for paging:
<template is="dom-bind">
<iron-pages selected="{{page}}" attr-for-selected="id">
<section id="login">
...
</section>
<section id="main">
...
</section>
</iron-pages>
</template>
But how can I implement a listener function that's fired when page changes? In custom elements, I'd use a declared property:
Polymer({
...
properties: {
page: {
type: String,
notify: true
}
},
pageChanged: function() {
// My listener function
}
});
So I tried setting properties and pageChanged to the template element:
var template = document.querySelector('template');
template.properties = { ... };
template.pageChanged = function() { ... };
But the properties property is already reserved.
Do you have an idea how to do that? Thanks in advance,
Felix
You can use a property observer:
Polymer({
properties: {
page: {
type: String,
notify: true
observer: 'pageChanged'
}
},
pageChanged: function(newPage,oldPage) {
// My listener function
}
});
You can add and observe:'functionName' in page property which actually changes and in that
functionName:function(){
fire('custom-event');
}
That is what you are trying to achieve I think.

Durandal 2 upgrade redirect issue

Hello and thanks for taking a look at my issue.
I have been migrating my SPA application to use the Durandal 2.0 library, following the sage advice from my oft savior, John Papa. And now that I have completed the upgrade process, I find a strange behavior (or a lack of behavior) when I try to navigate using my menu buttons. Specifically what isn't happening is the browser doesn't redirect to the new page. The interesting thing is that the browser address bar is populated properly and if I simply click in the address bar and press enter (hard reload), I am redirected as expected.
I've looked around and this is not caused due to any security check/redirect which I have seen other discussing elsewhere. Durandal code is unmodified.
js on pages can be quite trivial:
define([], function () {
console.log("welcome loaded");
var vm = {
title: 'Welcome'
};
return vm;
});
So my guess is its something in my configuration of durandal.
main.js:
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'transitions': '../Scripts/durandal/transitions',
'knockout': '../Scripts/knockout-2.3.0',
'bootstrap': '../Scripts/bootstrap',
'jquery': '../Scripts/jquery-1.9.1'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define('jquery', function () { return jQuery; });
define('knockout', ko);
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'],
function (system, app, viewLocator) {
// Enable debug message to show in the console
system.debug(true);
app.configurePlugins({
router: true,
dialog: true,
widget: true
});
app.start().then(function () {
toastr.options.positionClass = 'toast-bottom-right';
toastr.options.backgroundpositionClass = 'toast-bottom-right';
// When finding a viewmodel module, replace the viewmodel string
// with view to find it partner view.
viewLocator.useConvention();
// Adapt to touch devices
// app.adaptToDevice();
//Show the app by setting the root view model for our application.
app.setRoot('viewmodels/shell', 'entrance');
});
});
shell.js:
define(['../../Scripts/durandal/plugins/router', 'viewmodels/config', 'services/datacontext'], function (router, config, datacontext) {
function addSession(item) {
router.navigate(item.hash);
}
function boot() {
// $(".page-splash-message").text("Configuring routes...");
router.makeRelative({ moduleId: 'viewmodels' });
router.map(config.routes);
router.buildNavigationModel();
$(".page-splash-message").text("Let's make traxx..!");
return router.activate();
}
function failedInitialization(error) {
var msg = 'App initialization failed: ' + error.message;
}
return {
addSession: addSession,
adminRoutes: adminRoutes,
profileRoutes: profileRoutes,
visitorRoutes: visitorRoutes,
router: router,
activate: function () {
datacontext.primeEditData().then(boot).fail(failedInitialization);
}
};
});
routes in config.js
define(['../../Scripts/durandal/plugins/router'], function (router) {
toastr.options.timeOut = 4000;
toastr.options.positionClass = 'toast-bottom-right';
var startModule = 'Welcome';
var serviceName = 'api/Zepher';
var imageSettings = {
imageBasePath: '../content/images/photos/',
unknownPersonImageSource: 'unknown_person.jpg'
};
var routes = [
{ route: '', moduleId: 'home/welcome', title: 'Welcome', nav: false, },
{ route: 'Welcome', moduleId: 'home/welcome', title: 'Welcome', nav: false, },
{ route: 'NotFound', moduleId: 'home/notFound', title: 'Not Found', nav: false, },
{ route: 'Roadmap', moduleId: 'home/roadmap', title: 'Roadmap', nav: false, },
{ route: 'Register', moduleId: 'account/register', title: 'Register', nav: true, caption: '<i class="fa fa-user"></i> Register' },
{ route: 'RegisterAccounts', moduleId: 'account/registerAccounts', title: 'Register Accounts', nav: false, caption: '<i class="fa fa-key"></i> Register Accounts', },
];
return {
debugEnabled: ko.observable(true),
imageSettings: imageSettings,
servicetitle: serviceName,
startModule: startModule,
router: router,
routes: routes,
activate: function () {
console.log("config activate called");
router.makeRelative({moduleId: 'viewmodels'});
router.map(routes);
router.buildNavigationModel();
//sets up conventional mapping for
//unrecognized routes
router.mapUnknownRoutes('home/nontFound', 'not-found');
//activates the router
return router.activate();
// no longer needs a start module
}
};
});
found what I was missing in my upgrade, so I thought I'd share what I've learned.
Seems I forgot to update my Shell.html file.
From this:
<div>
<header>
<!--ko compose: {view: 'shared/nav', afterCompose: router.afterLogging, transition: 'entrance' } --><!--/ko-->
</header>
<section id="content" class="main">
<!--ko compose: {model: router.activeItem, afterCompose: router.afterCompose, transition: 'entrance', cacheViews: true } --><!--/ko-->
</section>
<footer>
<!--ko compose: {view: 'shared/footer'} --><!--/ko-->
</footer>
</div>
to This:
<div>
<header>
<!--ko compose: {view: 'shared/nav', afterCompose: router.afterLogging, transition: 'entrance' } --><!--/ko-->
</header>
<section id="content" class="main container-fluid page-host" data-bind="router: { transition: 'entrance', cacheViews: true }">
</section>
<footer>
<!--ko compose: {view: 'shared/footer'} --><!--/ko-->
</footer>
</div>

Resources