-1

Using SvelteKit (but is the same for each JS framework) I have a menu like this:

const menu = [
  { name: "players", url: "/players" },
  { name: "team", url: "/players/team" },
  { name: "player detail", url: "/players/[id]" }, // id can be a string here
  { name: "player's team coach detail", url: "/players/[id]/coach" }, // id can be a string here
  // and so on...
];

and I'm using it with a for loop using an <a> tag:

<a href="{url}" class:current="{page.url.pathname.startsWith(url)}">
  {name}
</a>

As you can see the class:current part is wrong because it marks as current all these urls:

  1. /players and
  2. /players/1 and so on player 2, 3...
  3. /players/team, this is not correct for me

This is not what I need.

I need a way to exclude the /players/team and the coach one.

Is there a way without using regexes?

4
  • Can you print the values of page.url.pathname and url ? I want to know if the possibility of class:current="{url.startsWith(page.url.pathname)}" works Commented Oct 4, 2023 at 13:14
  • kit.svelte.dev/docs/web-standards#url-apis this is the page.url object. Commented Oct 4, 2023 at 13:17
  • It looks like it uses the same as developer.mozilla.org/en-US/docs/Web/API/URL/pathname Commented Oct 4, 2023 at 13:26
  • pls give a output screenshot Commented Oct 4, 2023 at 14:21

4 Answers 4

1

You can try something like this:

function checkUrl(url) {
  const [, p1, p2, p3] = page.url.pathname.split('/');
  
  if(url === '/players') {
    return p1 === 'players' && !p2;
  }

  if(url === '/players/team') {
    return p1 === 'players' && p2 === 'team';
  }

  if(url === '/players/[id]') {
    return p1 === 'players' && !!p2 && p2 !== 'team' && !p3;
  }

  if(url === '/players/[id]/coach') {
    return p1 === 'players' && !!p2 && p3
    === 'coach';
  }

  return false;
}

...

<a href="{url}" class:current="{checkUrl(url)}">
  {name}
</a>

Example:

const menu = [
  { name: "players", url: "/players" },
  { name: "team", url: "/players/team" },
  { name: "player detail", url: "/players/[id]" },
  { name: "player's team coach detail", url: "/players/[id]/coach" }
  // and so on...
];

function checkUrl(url, pathname) {
  const [, p1, p2, p3] = pathname.split('/');
  
  if(url === '/players') {
    return p1 === 'players' && !p2;
  }

  if(url === '/players/team') {
    return p1 === 'players' && p2 === 'team';
  }

  if(url === '/players/[id]') {
    return p1 === 'players' && !!p2 && p2 !== 'team' && !p3;
  }
  
  if(url === '/players/[id]/coach') {
    return p1 === 'players' && !!p2 && p3
    === 'coach';
  }

  return false;
}

menu.forEach(obj => {
  console.log('url:', obj.url);
  console.log('/players', checkUrl(obj.url, '/players'));
  console.log('/players/', checkUrl(obj.url, '/players/'));
  console.log('/players/1', checkUrl(obj.url, '/players/1'));
  console.log('/players/str', checkUrl(obj.url, '/players/str'));
  console.log('/players/team', checkUrl(obj.url, '/players/team')); 
  console.log('/players/1/coach', checkUrl(obj.url, '/players/1/coach'));

  console.log('-----------------------------------')
})

Here is a more generic way to do this:

const menu = [
  { name: "players", url: "/players" },
  { name: "team", url: "/players/team" },
  { name: "player detail", url: "/players/[id]" },
  { name: "player's team coach detail", url: "/players/[id]/coach" }
  // and so on...
];

const excludedParams = {
    1: ['team'],
    2: ['coach']
};

function checkUrl(url, pathname) {
  const [, ...routeParams] = url.split('/');
  const [, ...params] = pathname.split('/');
  
  if(routeParams.at(-1) === '') {
    routeParams.pop();
  }
  
  if(params.at(-1) === '') {
    params.pop();
  }
  
  if(routeParams.length !== params.length) {
    return false;
  }
  
  const check = routeParams.every((routeParam, index) => {
    const currParam = params[index];
    if(routeParam.startsWith('[') && routeParam.endsWith(']')) {
      let check = !!currParam;
      if(excludedParams?.[index]?.length) {
        check &= !excludedParams[index].includes(currParam);
      }
      return check;
    } else {
      return routeParam === currParam;
    }
  })
 
  return check;
}

menu.forEach(obj => {
  console.log('url:', obj.url);
  console.log('/players', checkUrl(obj.url, '/players'));
  console.log('/players/', checkUrl(obj.url, '/players/'));
  console.log('/players/1', checkUrl(obj.url, '/players/1'));
  console.log('/players/str', checkUrl(obj.url, '/players/str'));
  console.log('/players/team', checkUrl(obj.url, '/players/team')); 
  console.log('/players/str/coach', checkUrl(obj.url, '/players/1/coach'));
  console.log('-----------------------------------')
})

Sign up to request clarification or add additional context in comments.

11 Comments

Thanks. And what if player's ID is not a number but a string instead?
Can you elaborate ? what do you expect if url === '/players/team' and page.url.pathname === '/players/team' ? and for url === '/players/[id]' and page.url.pathname === '/players/team' ?
You used isNaN but I have also strings for player's ID like for example /players/ABC.
@FredHors Check my update, you can comment && P2 !== 'team' and if(url === '/players/team') { ... depending on your requirements
maybe is's worth passing the excludedParams as an array to the checkUrl function? I'm with @Fraction on this one. more details on what specifically needs to be generic would go a long way. Is the requirement that the class:current only be true on urls that contain the specific player ID? in which case you can grab the ID ie the [slug] via the load function as shown here: stackoverflow.com/questions/65930303/… and kit.svelte.dev/docs/routing
|
0

Use a built-in Svelte function path which returns the current route's path without the domain.

1st, store the current path in a variable using the svelte:window directive:

<svelte:window bind:currentPath="path" />

Then, inside for loop, use the path function to get the URL path of each menu item and compare it to the current path using the === operator. This will only add the current class to the menu item that matches the current path exactly, excluding the /players/team and coach URLs.

<a href="{url}" class:current="{path === url.path}">
  {name}
</a>

more precise and does not require the use of regex.

3 Comments

What if I have routes like: /players, /players/team and /players/abc?
@FredHors Using === will only add the current class to links with an exact match to the url in the menu. To exclude these URLs, you can use the !== operator along with an additional check for /players using the startsWith() method.
let me know what exactly to include/exclude, and i will update the answer with same
0

Please try something like this:

function isCurrentPage(pathname, url) {
  const segmentsInPath = pathname.split('/').filter(Boolean);
  const segmentsInUrl = url.split('/').filter(Boolean);  
  if (segmentsInPath.length > segmentsInUrl.length) return false;
  return pathname.startsWith(url);
}
<a href="{url}" class:current="{isCurrentPage(page.url.pathname, url)}">
  {name}
</a>

This code doesn't make any assumptions about the number of segments there are in the route or even the prefix "players", since I assume you want it to be generic.

Comments

0

By using regexes you can accomplish the same:

const menu = [
  { name: "players", rurl: /\/players/, url: "/players" },
  { name: "team", rurl: /\/players\/team/, url: "/players/team" },
  { name: "player detail", rurl: /\/players\/[\d]/, url: "/players/[id]" },
  { name: "player's team coach detail", rurl: /\/players\/[\d]\/coach/,url: "/players/[id]/coach" }
  // and so on...
];
<a href="{url}" class:current="{page.url.pathname.match(rurl)}">
  {name}
</a>

What you are doing is performing a match of the URL regex version against the page.url.pathname that I assume it is a String object if not then you can use toString or parse it to string and do the match.

I got the information about regexes from MDN. You can search there for any other customization you need to do.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.