Sometimes it is desirable to persist scroll positions between page visits.
Turbolinks resets scroll position after loading the data.
How can I disable it for particular elements?
My solution in ES6:
const turbolinksPersistScroll = () => {
const persistScrollDataAttribute = 'turbolinks-persist-scroll'
let scrollPosition = null
let enabled = false
document.addEventListener('turbolinks:before-visit', (event) => {
if (enabled)
scrollPosition = window.scrollY
else
scrollPosition = null
enabled = false
})
document.addEventListener('turbolinks:load', (event) => {
const elements = document.querySelectorAll(`[data-${persistScrollDataAttribute}="true"]`)
for (let i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', () => {
enabled = true
})
}
if (scrollPosition)
window.scrollTo(0, scrollPosition)
})
}
turbolinksPersistScroll()
And add on your links data-turbolinks-persist-scroll=true on links you want persist scrollbar position.
<a href="..." data-turbolinks-persist-scroll=true>Link</a>
This works for me, also with link_to remote: true.
Use the following javascript to persist scrolls. I have created a selector that matches all elements with class turbolinks-disable-scroll. Before loading,the script saves the scroll position and after loading it loads the saved positions.
// persist scrolls
// pirated from https://github.com/turbolinks/turbolinks-classic/issues/205
var elementsWithPersistentScrolls, persistentScrollsPositions;
elementsWithPersistentScrolls = ['.turbolinks-disable-scroll'];
persistentScrollsPositions = {};
$(document).on('turbolinks:before-visit', function() {
var i, len, results, selector;
persistentScrollsPositions = {};
results = [];
for (i = 0, len = elementsWithPersistentScrolls.length; i < len; i++) {
selector = elementsWithPersistentScrolls[i];
results.push(persistentScrollsPositions[selector] = $(selector).scrollTop());
}
return results;
});
$(document).on('turbolinks:load', function() {
var results, scrollTop, selector;
results = [];
for (selector in persistentScrollsPositions) {
scrollTop = persistentScrollsPositions[selector];
results.push($(selector).scrollTop(scrollTop));
}
return results;
});
It seems like there are two approaches to this problem.
Preserve flagged elements (#vedant1811's answer)
Preserve body scroll for flagged links
The second approach is the one that I've been looking for and couldn't find anywhere, so I'll provide my answer to that here.
The solution here is very similar to that of the first approach, but perhaps a little simpler. The idea is to grab the current scroll position of the body when an element is clicked, and then scroll to that position after the page is loaded:
Javascript
Turbolinks.scroll = {}
$(document).on('click', '[data-turbolinks-scroll=false]', function(e){
Turbolinks.scroll['top'] = $('body').scrollTop();
})
$(document).on('page:load', function() {
if (Turbolinks.scroll['top']) {
$('body').scrollTop(Turbolinks.scroll['top']);
}
Turbolinks.scroll = {};
});
Markup
<a href='/' data-turbolinks-scroll='false'>Scroll preserving link</a>
I use a scroll attribute on the Turbolinks object to store my scroll position when a [data-turbolinks-scroll=false] link is clicked, then after I scroll the page I clear this attribute.
It is important that you clear the attribute (Turbolinks.scroll = {}) otherwise, subsequent clicks on non-flagged anchor links will continue to scroll you to the same position.
Note: depending on the specific styling of html and body you may need to use the scroll offset from both. An example of how this might be accomplished is:
Turbolinks.scroll = {};
$(document).on('click', '[data-turbolinks-scroll=false]', function (e) {
Turbolinks.scroll['top'] = {
html: $("html").scrollTop(),
body: $("body").scrollTop()
}
});
$(document).on('turbolinks:load', function() {
if (Turbolinks.scroll['top']) {
$('html').scrollTop(Turbolinks.scroll['top']['html']);
$('body').scrollTop(Turbolinks.scroll['top']['body']);
}
Turbolinks.scroll = {};
});
I noticed that sometimes scroll is going up and then only down. This version prevents such behaviour:
const persistScrollDataAttribute = 'turbolinks-persist-scroll';
let scrollPosition = null;
const turbolinksPersistScroll = () => {
if (scrollPosition) {
window.scrollTo(0, scrollPosition);
scrollPosition = null;
}
const elements = document.querySelectorAll(`[data-${persistScrollDataAttribute}="true"]`)
for (let i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', () => {
document.addEventListener("turbolinks:before-render", () => {
scrollPosition = window.scrollY;
}, {once: true})
})
}
}
document.addEventListener('turbolinks:load', turbolinksPersistScroll);
document.addEventListener('turbolinks:render', turbolinksPersistScroll);
Related
I build an webpage with angular, each module is an component it has an animation in it but it run's only when the page opens but i need to perform the animation while the component is visibile on the screen. i just tried below like hide and show the component by checking the scrollY of the page. is there any better way to do it?
#HostListener('window:scroll', ['$event']) onWindowScroll(e: any) {
if (window.pageYOffset < 180) {
this.heroShown = 0;
} else {
this.heroShown = 1;
}
console.log(e.target['scrollingElement'].scrollTop);
console.log(document.body.scrollTop);
console.log(window.pageYOffset);
}
`
for that you can use a Intersection Observer.
The observer fires an event when the element is visible.
So when the event fires you can start your animation.
private createObserver() {
const options = {
rootMargin: '0px',
threshold: this.threshold,
};
const isIntersecting = (entry: IntersectionObserverEntry) =>
entry.isIntersecting || entry.intersectionRatio > 0;
this.observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (isIntersecting(entry)) {
this.subject$.next({ entry, observer });
}
});
}, options);
}
a other way to archive this is using a framwork like gsap
There you can use something like a scrolltrigger.
Check the docs here.
How can I reverse the events in the list views, so that the event with the most futuristic date appears at the beginning (top)?
#F.Mora your solution is almost perfect but in our case we add some custom classNames and have multiple items under each headline.
Here is our enhanced version :
eventAfterAllRender: function(view) {
var renderedEvents = $('.fc-list-table tr');
var reorderedEvents = [];
var blockEvents = null;
renderedEvents.map(function(key, event) {
if ($(event).hasClass('fc-list-heading')) {
if (blockEvents) {
reorderedEvents.unshift(blockEvents.children());
}
blockEvents = $('<tbody></tbody>');
}
blockEvents.append(event);
});
reorderedEvents.unshift(blockEvents.children());
$('.fc-list-table tbody').html(reorderedEvents);
}
#CarComp,
As ADyson commented in the comment to the OP, your best bet if you want do not want to deal with the dom after the html has been rendered is to download the source and make the modification there in the ListView renderSegList function.
Reverse the order of iteration through the list that it is being created and then you will have what you are looking for.
This will, of course, apply to all ListView implementations of the calendar. There would need to be an option added to toggle back and forth, which would be a bit more involved.
For anyone still looking for this, inverted event lists using jquery:
eventAfterAllRender: function(view) {
var eventosRendered = $('#timeline tr');
var eventosInversa = [];
var headingPendiente = null;
eventosRendered.map(function(key, evento) {
switch(evento.className) {
case 'fc-list-heading':
if (headingPendiente) {
eventosInversa.unshift(headingPendiente);
}
headingPendiente = evento;
break;
case 'fc-list-item':
eventosInversa.unshift(evento);
break;
}
});
eventosInversa.unshift(headingPendiente);
$('#timeline tbody').append(eventosInversa);
}
Here's the version I use (fullCalendar v4):
datesRender: function(info) {
var list = $(info.el).find('.fc-list-table tbody');
list.find('.fc-list-heading').each((i,heading) => {
var children = $(heading).nextUntil('.fc-list-heading')
list.prepend(children)
list.prepend(heading)
})
},
I used this for fullCalendar v5. It´s based on #Yo1 answer
eventsSet: function(dateInfo){
var renderedEvents = $('.fc-list-table tr');
var reorderedEvents = [];
var blockEvents = null;
renderedEvents.map(function(key, event) {
if ($(event).hasClass('fc-list-day')) {
if (blockEvents) {
reorderedEvents.unshift(blockEvents.children());
}
blockEvents = $('<tbody></tbody>');
}
blockEvents.append(event);
});
if (blockEvents){
reorderedEvents.unshift(blockEvents.children());
$('.fc-list-table tbody').html(reorderedEvents);
}
},
I have a side menu and when it's open, the body can be partially seen. My side menu might be long so you could scroll on it. But when the menu is at the bottom you then scroll on the body, and I don't want this behaviour.
Similar to Scrolling only content div, others should be fixed but I'm using React. Other content should be scrollable when my side menu is closed. Think of the content as side menu in the example in the link. So far I'm using the same technique provided by that answer but it's ugly (kinda jQuery):
preventOverflow = (menuOpen) => { // this is called when side menu is toggled
const body = document.getElementsByTagName('body')[0]; // this should be fixed when side menu is open
if (menuOpen) {
body.className += ' overflow-hidden';
} else {
body.className = body.className.replace(' overflow-hidden', '');
}
}
// css
.overflow-hidden {
overflow-y: hidden;
}
What should I do with Reactjs?
You should make a meta component in react to change things on the body as well as changing things like document title and things like that. I made one a while ago to do that for me. I'll add it here.
Usage
render() {
return (
<div>
<DocumentMeta bodyClasses={[isMenuOpen ? 'no-scroll' : '']} />
... rest of your normal code
</div>
)
}
DocumentMeta.jsx
import React from 'react';
import _ from 'lodash';
import withSideEffect from 'react-side-effect';
var HEADER_ATTRIBUTE = "data-react-header";
var TAG_NAMES = {
META: "meta",
LINK: "link",
};
var TAG_PROPERTIES = {
NAME: "name",
CHARSET: "charset",
HTTPEQUIV: "http-equiv",
REL: "rel",
HREF: "href",
PROPERTY: "property",
CONTENT: "content"
};
var getInnermostProperty = (propsList, property) => {
return _.result(_.find(propsList.reverse(), property), property);
};
var getTitleFromPropsList = (propsList) => {
var innermostTitle = getInnermostProperty(propsList, "title");
var innermostTemplate = getInnermostProperty(propsList, "titleTemplate");
if (innermostTemplate && innermostTitle) {
return innermostTemplate.replace(/\%s/g, innermostTitle);
}
return innermostTitle || "";
};
var getBodyIdFromPropsList = (propsList) => {
var bodyId = getInnermostProperty(propsList, "bodyId");
return bodyId;
};
var getBodyClassesFromPropsList = (propsList) => {
return propsList
.filter(props => props.bodyClasses && Array.isArray(props.bodyClasses))
.map(props => props.bodyClasses)
.reduce((classes, list) => classes.concat(list), []);
};
var getTagsFromPropsList = (tagName, uniqueTagIds, propsList) => {
// Calculate list of tags, giving priority innermost component (end of the propslist)
var approvedSeenTags = {};
var validTags = _.keys(TAG_PROPERTIES).map(key => TAG_PROPERTIES[key]);
var tagList = propsList
.filter(props => props[tagName] !== undefined)
.map(props => props[tagName])
.reverse()
.reduce((approvedTags, instanceTags) => {
var instanceSeenTags = {};
instanceTags.filter(tag => {
for(var attributeKey in tag) {
var value = tag[attributeKey].toLowerCase();
var attributeKey = attributeKey.toLowerCase();
if (validTags.indexOf(attributeKey) == -1) {
return false;
}
if (!approvedSeenTags[attributeKey]) {
approvedSeenTags[attributeKey] = [];
}
if (!instanceSeenTags[attributeKey]) {
instanceSeenTags[attributeKey] = [];
}
if (!_.has(approvedSeenTags[attributeKey], value)) {
instanceSeenTags[attributeKey].push(value);
return true;
}
return false;
}
})
.reverse()
.forEach(tag => approvedTags.push(tag));
// Update seen tags with tags from this instance
_.keys(instanceSeenTags).forEach((attr) => {
approvedSeenTags[attr] = _.union(approvedSeenTags[attr], instanceSeenTags[attr])
});
instanceSeenTags = {};
return approvedTags;
}, []);
return tagList;
};
var updateTitle = title => {
document.title = title || document.title;
};
var updateBodyId = (id) => {
document.body.setAttribute("id", id);
};
var updateBodyClasses = classes => {
document.body.className = "";
classes.forEach(cl => {
if(!cl || cl == "") return;
document.body.classList.add(cl);
});
};
var updateTags = (type, tags) => {
var headElement = document.head || document.querySelector("head");
var existingTags = headElement.querySelectorAll(`${type}[${HEADER_ATTRIBUTE}]`);
existingTags = Array.prototype.slice.call(existingTags);
// Remove any duplicate tags
existingTags.forEach(tag => tag.parentNode.removeChild(tag));
if (tags && tags.length) {
tags.forEach(tag => {
var newElement = document.createElement(type);
for (var attribute in tag) {
if (tag.hasOwnProperty(attribute)) {
newElement.setAttribute(attribute, tag[attribute]);
}
}
newElement.setAttribute(HEADER_ATTRIBUTE, "true");
headElement.insertBefore(newElement, headElement.firstChild);
});
}
};
var generateTagsAsString = (type, tags) => {
var html = tags.map(tag => {
var attributeHtml = Object.keys(tag)
.map((attribute) => {
const encodedValue = HTMLEntities.encode(tag[attribute], {
useNamedReferences: true
});
return `${attribute}="${encodedValue}"`;
})
.join(" ");
return `<${type} ${attributeHtml} ${HEADER_ATTRIBUTE}="true" />`;
});
return html.join("\n");
};
var reducePropsToState = (propsList) => ({
title: getTitleFromPropsList(propsList),
metaTags: getTagsFromPropsList(TAG_NAMES.META, [TAG_PROPERTIES.NAME, TAG_PROPERTIES.CHARSET, TAG_PROPERTIES.HTTPEQUIV, TAG_PROPERTIES.CONTENT], propsList),
linkTags: getTagsFromPropsList(TAG_NAMES.LINK, [TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF], propsList),
bodyId: getBodyIdFromPropsList(propsList),
bodyClasses: getBodyClassesFromPropsList(propsList),
});
var handleClientStateChange = ({title, metaTags, linkTags, bodyId, bodyClasses}) => {
updateTitle(title);
updateTags(TAG_NAMES.LINK, linkTags);
updateTags(TAG_NAMES.META, metaTags);
updateBodyId(bodyId);
updateBodyClasses(bodyClasses)
};
var mapStateOnServer = ({title, metaTags, linkTags}) => ({
title: HTMLEntities.encode(title),
meta: generateTagsAsString(TAG_NAMES.META, metaTags),
link: generateTagsAsString(TAG_NAMES.LINK, linkTags)
});
var DocumentMeta = React.createClass({
propTypes: {
title: React.PropTypes.string,
titleTemplate: React.PropTypes.string,
meta: React.PropTypes.arrayOf(React.PropTypes.object),
link: React.PropTypes.arrayOf(React.PropTypes.object),
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
bodyClasses: React.PropTypes.array,
},
render() {
if (Object.is(React.Children.count(this.props.children), 1)) {
return React.Children.only(this.props.children);
} else if (React.Children.count(this.props.children) > 1) {
return (
<span>
{this.props.children}
</span>
);
}
return null;
},
});
DocumentMeta = withSideEffect(reducePropsToState, handleClientStateChange, mapStateOnServer)(DocumentMeta);
module.exports = DocumentMeta;
This component could probably be changed a little for your case (withSideEffect is used for both client and server side rendering... if you arent using server side rendering then its probably not completely necessary) but the component will work on client side rendering if you would like to use it there as well.
ReactJS doesn't have direct access to the <body> element, and that's the element that needs to have its overflow-y style changed. So while what you're doing isn't perhaps the prettiest code, it's not entirely wrong either.
The only real suggestion I'd give is (shudder) using inline styles on the body instead of a classname so as to avoid having to introduce the CSS declaration. As long as your menu is the only thing responsible for updating the overflow-y attribute, there's no reason you can't use an inline style on it. Mashing that down with the ?: operator results in fairly simple code:
body.style.overflowY = menuOpen ? "hidden" : "";
And then you can just delete the .overflow-hidden class in its entirety.
If for some reason multiple things are managing the overflow state of the body, you might want to stick with classnames and assign a unique one for each thing managing it, something like this:
if (menuOpen) {
body.className += ' menu-open';
}
else {
// Use some tricks from jQuery to remove the "menu-open" class more elegantly.
var className = " " + body.className + " ";
className = className.replace(" overflow-hidden ", " ").replace(/\s+/, " ");
className = className.substr(1, className.length - 2);
}
CSS:
body.menu-open {
overflow-y: hidden;
}
I can set css properties on an element in a directive. But I cannot retrieve css properties on an element using the same method, it just returns an empty string.
i.e: var test = element.css("background-size"); //does not work!
What am I doing wrong? See my link handler in my directive below:
link: function($scope, element, attrs) {
//debugger;
//handler for close button:
//its the first child within the parent element:
$scope.closeBtn = angular.element(element.children()[0]);
//save the background image so we can toggle its visibility:
$scope.backgroundImg = element.css("background","url(../../a0DK0000003XvBYMA0/assets/images/tabbed_panel_bkgd.png) no-repeat") ;//className:
element.css("background-position","0px 35px");
element.css("background-size", "924px 580px");
//above I was able to set css properties, but why can't I retrieve css properties like this??:
var test = element.css("background-size");
$scope.closeBtn.bind('click',function(){
TweenLite.to(element, .75, {top:"635px",ease:Power2.easeOut,
onComplete:function(){
$scope.opened = false;
$scope.closeBtn.css('opacity',0);
} });
})
//hander to raise tab panel:
element.bind('click', function() {
if(!$scope.opened){
//debugger;
$scope.closeBtn.css('opacity',1);
TweenLite.to(element, .75, {top:"150px",ease:Power2.easeOut});
$scope.opened = true;
}
});
}
I took a step back from my question and realized if I am trying to retrieve css properties like used to do with JQuery then I am probably not applying a solution in the "angular way". My original problem is that I needed to store css properties so I coule re apply them later. So instead of that approach, I used the ng-class directive to toggle the classes so I would not have to store anything.
<html>
<body>
<tabbed-Panel ng-class="btmTabPanelClass" >
<div ng-show="opened" class="tabPanelCloseBtn"> </div>
<tabs>
<pane ng-repeat="pane in panes" heading="{{pane.title}}" active="pane.active">
<div class ="tabPanelContent" ng-include src="activeContent()"></div>
</pane>
</tabs>
</tabbed-Panel>
</div
</body>
</html>
angular.module('directives', ['baseModule','ui.bootstrap'])
.directive('tabbedPanel',['$animator',function($animator) {
//debugger;
return {
//scope:{},
restrict:"E",
//add controller to here
controller:function($scope){
//debugger;
$scope.bTabClicked = 0;
$scope.curTabIdx = 0;
$scope.opened = false;
$scope.closeBtn = null;
$scope.arClasses = ["bottomTabPanel", " bp_off"];
$scope.btmTabPanelClass = $scope.arClasses[0] + $scope.arClasses[1] ;
//get the tabs from the flows.json so we can create a model for the tab panel!
$scope.panes = $scope.flows[$scope.getCurFlowIdx()].array_data[$scope.getCurPageIdx()].tab_data;
//first tab is active by default:
//$scope.panes[0].active = true;
//set the content for the current tab:
$scope.activeContent = function() {
for (var i=0;i<$scope.panes.length;i++) {
if ($scope.panes[i].active) {
$scope.curTabIdx = i;
return $scope.panes[i].content;
}
}
};
//tab click watcher (to make sure user clicks on tab and not tab container):
$scope.$watch('activeContent()', function(paneIndex) {
++$scope.bTabClicked;
});
//--------------------------------------------------
},
link: function($scope, element, attrs) {
//debugger;
//handler for close button:
//its the first child within the parent element:
$scope.closeBtn = angular.element(element.children()[0]);
$scope.closeBtn.bind('click',function(){
// set all tabs to inactive:
$scope.bTabClicked = 0;
for (var i=0;i<$scope.panes.length;i++)
$scope.panes[i].active = false;
TweenLite.to(element, .75, {top:"635px",ease:Power2.easeOut,
onComplete:function(){
$scope.opened = false;
$scope.btmTabPanelClass = $scope.arClasses[0] + $scope.arClasses[1] ;
$scope.$apply(); //force binding to update
$scope.bTabClicked = 0;
} });
})
/*hander to raise tab panel:*/
element.bind('click', function() {
if(!$scope.opened && $scope.bTabClicked){
//debugger;
TweenLite.to(element, .75, {top:"150px",ease:Power2.easeOut});
$scope.opened = true;
$scope.btmTabPanelClass = $scope.arClasses[0] ;
$scope.$apply(); //force binding to update
}
else
$scope.bTabClicked = 0;
});
}
};
}]);
You can access the CSS style of an Angular element in a directive's link function by
var style = window.getComputedStyle(element[0]),
And then access the value of any CSS rule like such:
var color = style.getPropertyValue('color');
I'm building a responsive site that uses javascript to create a select nav for the primary navigation menu when the viewport is less than 800px wide. This select nav works as planned.
Now, I'm trying to add a SECOND responsive select nav to a couple of pages (pages that obviously include the primary navigation menu). I've duplicated the script (the script used to enable the primary navigation menu to become a select nav), and tried renaming a couple variables to get the secondary navigation menu to act responsively (similar to the primary navigation menu). The responsive select nav appears for the secondary navigation menu when the viewport goes down below 800px, however, when you select a secondary page from this menu, nothing happens. The user isn't taken to a new secondary page.
Can you help me troubleshoot this?
I tried to set up a jfiddle http://jsfiddle.net/R3AD3/
Here is the script for the primary navigation menu:
function selectnav() {
var select = document.createElement('select');
var first = document.createElement('option');
first.innerHTML = 'Main Navigation';
first.setAttribute('selected', 'selected');
select.setAttribute('id', 'mobile');
select.appendChild(first);
var nav = document.getElementById('nav');
var loadLinks = function(element, hyphen, level) {
var e = element;
var children = e.children;
for(var i = 0; i < e.children.length; ++i) {
var currentLink = children[i];
switch(currentLink.nodeName) {
case 'A':
var option = document.createElement('option');
option.innerHTML = (level++ < 1 ? '' : hyphen) + currentLink.innerHTML;
option.value = currentLink.href;
select.appendChild(option);
break;
default:
if(currentLink.nodeName === 'UL') {
(level < 2) || (hyphen += hyphen);
}
loadLinks(currentLink, hyphen, level);
break;
}
}
}
loadLinks(nav, '- ', 0);
nav.appendChild(select);
var mobile = document.getElementById('mobile');
if(mobile.addEventListener) {
mobile.addEventListener('change', function () {
window.location.href = mobile.options[mobile.selectedIndex].value;
});
} else if(mobile.attachEvent) {
mobile.attachEvent('onchange', function () {
window.location.href = mobile.options[mobile.selectedIndex].value;
});
} else {
mobile.onchange = function () {
window.location.href = mobile.options[mobile.selectedIndex].value;
}
}
}
Here is the script that I duplicated... I then renamed a couple variables:
function selectnav_EP() {
var select = document.createElement('select');
var first = document.createElement('option');
first.innerHTML = 'Electric Playground Nav';
first.setAttribute('selected', 'selected');
select.setAttribute('id', 'mobile');
select.appendChild(first);
var nav = document.getElementById('nav_EP');
var loadLinks = function(element, hyphen, level) {
var e = element;
var children = e.children;
for(var i = 0; i < e.children.length; ++i) {
var currentLink = children[i];
switch(currentLink.nodeName) {
case 'A':
var option = document.createElement('option');
option.innerHTML = (level++ < 1 ? '' : hyphen) + currentLink.innerHTML;
option.value = currentLink.href;
select.appendChild(option);
break;
default:
if(currentLink.nodeName === 'UL') {
(level < 2) || (hyphen += hyphen);
}
loadLinks(currentLink, hyphen, level);
break;
}
}
}
loadLinks(nav, '- ', 0);
nav.appendChild(select);
var mobile = document.getElementById('mobile');
if(mobile.addEventListener) {
mobile.addEventListener('change', function () {
window.location.href = mobile.options[mobile.selectedIndex].value;
});
} else if(mobile.attachEvent) {
mobile.attachEvent('onchange', function () {
window.location.href = mobile.options[mobile.selectedIndex].value;
});
} else {
mobile.onchange = function () {
window.location.href = mobile.options[mobile.selectedIndex].value;
}
}
}
In the footer, I call upon both of these scripts.
Here is the exact page to my website
http://brianlueck.com/wordpress/electric-playground/
Narrow your browser window below 800px to see the responsive select navs appear... you'll notice that the primary navigation menu works fine, however, the secondary navigation menu doesn't function properly because it doesn't link off to the pages it should link to.
Can anyone help?