jackweirdy
jackweirdy

Reputation: 5850

Add trailing slash to url (if not present)

I'm making a small web app in which a user enters a server URL from which it pulls a load of data with an AJAX request. Since the user has to enter the URL manually, people generally forget the trailing slash, even though it's required (as some data is appended to the url entered). I need a way to check if the slash is present, and if not, add it.

This seems like a problem that jQuery would have a one-liner for, does anyone know how to do this or should I write a JS function for it?

Upvotes: 60

Views: 72287

Answers (10)

Ooker
Ooker

Reputation: 3032

I adopt @Terbiy's answer and significantly modify it by:

  • Using the native URL() construction instead of the parseUrl() function,
  • Adding TypeScript
  • Removing unnecessary variables and shortening some expressions
  • Keeping on account that there are directories with dots in the last section of the path, so we can only hope that the file extension being in used (if any) is a common one
  • Removing empty parameter or fragment so the URL will always have slash whenever possible

I think it will work for all cases.

/**
 * Slash is possible to add to the end of url in following cases:
 * - There is no slash standing as last symbol of URL.
 * - There is no file extension (or there is no dot inside the last section of the path).
 * - There is no parameter (even empty one — a single ? at the end of URL).
 * - There is no link to a fragment (even empty one — a single # mark at the end of URL).
 */
export function appendSlashToUrlIfIsPossible(url: string) {
  /** Removing empty parameter or fragment so the URL will always have slash if possible */
  const urlWithNoEmptyParameterOrFragment = url.replace(/#$/g, "").replace(/\?$/g, "");

  const parsedUrl = new URL(urlWithNoEmptyParameterOrFragment);

  /** There are directories with dots in the last section of the path, so we can only hope that the file extension being in used (if any) is a common one */
  const noFileExtension = !/\.(htm|html|jpg|png|gif|pdf|php|doc|docx)$/.test(parsedUrl.pathname);

  const noParameter = !parsedUrl.search;
  const noLinkToFragment = !parsedUrl.hash;

  /** All checks above cannot guarantee that there is no '?' or '#' symbol at the end of URL. It is required to be checked manually */
  const noTrailingSlashAlready = !/\/$/.test(parsedUrl.href);

  const slashAppendingIsPossible = noFileExtension && noParameter && noLinkToFragment && noTrailingSlashAlready;
  
  if (slashAppendingIsPossible) return `${parsedUrl.href}/`;
  return parsedUrl.href;
}

Test:

const tests: string[] = [
  "https://quảcầu.com",
  "https://example.com/path",
  "https://example.com/path/",
  "https://example.com/path?",
  "https://example.com/path?#",
  "https://example.com/path/?#",
  "https://example.com/path/?parameter#",
  "https://example.com/path/?parameter#fragment",
  "https://example.com/path/file.pdf",
  "https://example.com/path/file.pdf?",
  "https://example.com/path/path.with.dot",
];

for (const url of tests) {
  console.log(appendSlashToUrlIfIsPossible(url));
}

// https://xn--qucu-hr5aza.com/
// https://example.com/path/
// https://example.com/path/
// https://example.com/path/
// https://example.com/path/
// https://example.com/path/
// https://example.com/path/?parameter
// https://example.com/path/?parameter#fragment
// https://example.com/path/file.pdf
// https://example.com/path/file.pdf
// https://example.com/path/path.with.dot/

Upvotes: 1

Chad Johnson
Chad Johnson

Reputation: 21895

This works as well:

url = url.replace(/\/$|$/, '/');

Example:

let urlWithoutSlash = 'https://www.example.com/path';
urlWithoutSlash = urlWithoutSlash.replace(/\/$|$/, '/');
console.log(urlWithoutSlash);

let urlWithSlash = 'https://www.example.com/path/';
urlWithSlash = urlWithSlash.replace(/\/$|$/, '/');
console.log(urlWithSlash);

Output:

https://www.example.com/path/
https://www.example.com/path/

It replaces either the trailing slash or no trailing slash with a trailing slash. So if the slash is present, it replaces it with one (essentially leaving it there); if one is not present, it adds the trailing slash.

Upvotes: 14

Arik
Arik

Reputation: 6501

The URL class is pretty awesome - it helps us change the path and takes care of query parameters and fragment identifiers

function addTrailingSlash(u) {
    const url = new URL(u);
    url.pathname += url.pathname.endsWith("/") ? "" : "/";
    return url.toString();
}

addTrailingSlash('http://example.com/slug?page=2');
// result: "http://example.com/slug/?page=2"

You can read more about URL on MDN

Upvotes: 6

Nicolas Guérinet
Nicolas Guérinet

Reputation: 2226

For those who use different inputs: like http://example.com or http://example.com/eee. It should not add a trailling slash in the second case.

There is the serialization option using .href which will add trailing slash only after the domain (host).

In NodeJs,

You would use the url module like this: 
const url = require ('url');
let jojo = url.parse('http://google.com')
console.log(jojo);

In pure JS, you would use

var url = document.getElementsByTagName('a')[0];
var myURL = "http://stackoverflow.com";
console.log(myURL.href);

Upvotes: 0

Terbiy
Terbiy

Reputation: 700

Not every URL can be completed with slash at the end. There are at least several conditions that do not allow one:

  • String after last existing slash is something like index.html.
  • There are parameters: /page?foo=1&bar=2.
  • There is link to fragment: /page#tomato.

I have written a function for adding slash if none of the above cases are present. There are also two additional functions for checking the possibility of adding slash and for breaking URL into parts. Last one is not mine, I've given a link to the original one.

const SLASH = '/';

function appendSlashToUrlIfIsPossible(url) {
  var resultingUrl = url;
  var slashAppendingPossible = slashAppendingIsPossible(url);

  if (slashAppendingPossible) {
    resultingUrl += SLASH;
  }

  return resultingUrl;
}

function slashAppendingIsPossible(url) {
  // Slash is possible to add to the end of url in following cases:
  //  - There is no slash standing as last symbol of URL.
  //  - There is no file extension (or there is no dot inside part called file name).
  //  - There are no parameters (even empty ones — single ? at the end of URL).
  //  - There is no link to a fragment (even empty one — single # mark at the end of URL).
  var slashAppendingPossible = false;

  var parsedUrl = parseUrl(url);

  // Checking for slash absence.
  var path = parsedUrl.path;
  var lastCharacterInPath = path.substr(-1);
  var noSlashInPathEnd = lastCharacterInPath !== SLASH;

  // Check for extension absence.
  const FILE_EXTENSION_REGEXP = /\.[^.]*$/;
  var noFileExtension = !FILE_EXTENSION_REGEXP.test(parsedUrl.file);

  // Check for parameters absence.
  var noParameters = parsedUrl.query.length === 0;
  // Check for link to fragment absence.
  var noLinkToFragment = parsedUrl.hash.length === 0;

  // All checks above cannot guarantee that there is no '?' or '#' symbol at the end of URL.
  // It is required to be checked manually.
  var NO_SLASH_HASH_OR_QUESTION_MARK_AT_STRING_END_REGEXP = /[^\/#?]$/;
  var noStopCharactersAtTheEndOfRelativePath = NO_SLASH_HASH_OR_QUESTION_MARK_AT_STRING_END_REGEXP.test(parsedUrl.relative);

  slashAppendingPossible = noSlashInPathEnd && noFileExtension && noParameters && noLinkToFragment && noStopCharactersAtTheEndOfRelativePath;

  return slashAppendingPossible;
}

// parseUrl function is based on following one:
// http://james.padolsey.com/javascript/parsing-urls-with-the-dom/.
function parseUrl(url) {
  var a = document.createElement('a');
  a.href = url;

  const DEFAULT_STRING = '';

  var getParametersAndValues = function (a) {
    var parametersAndValues = {};

    const QUESTION_MARK_IN_STRING_START_REGEXP = /^\?/;
    const PARAMETERS_DELIMITER = '&';
    const PARAMETER_VALUE_DELIMITER = '=';
    var parametersAndValuesStrings = a.search.replace(QUESTION_MARK_IN_STRING_START_REGEXP, DEFAULT_STRING).split(PARAMETERS_DELIMITER);
    var parametersAmount = parametersAndValuesStrings.length;

    for (let index = 0; index < parametersAmount; index++) {
      if (!parametersAndValuesStrings[index]) {
        continue;
      }

      let parameterAndValue = parametersAndValuesStrings[index].split(PARAMETER_VALUE_DELIMITER);
      let parameter = parameterAndValue[0];
      let value = parameterAndValue[1];

      parametersAndValues[parameter] = value;
    }

    return parametersAndValues;
  };

  const PROTOCOL_DELIMITER = ':';
  const SYMBOLS_AFTER_LAST_SLASH_AT_STRING_END_REGEXP = /\/([^\/?#]+)$/i;
  // Stub for the case when regexp match method returns null.
  const REGEXP_MATCH_STUB = [null, DEFAULT_STRING];
  const URL_FRAGMENT_MARK = '#';
  const NOT_SLASH_AT_STRING_START_REGEXP = /^([^\/])/;
  // Replace methods uses '$1' to place first capturing group.
  // In NOT_SLASH_AT_STRING_START_REGEXP regular expression that is the first
  // symbol in case something else, but not '/' has taken first position.
  const ORIGINAL_STRING_PREPENDED_BY_SLASH = '/$1';
  const URL_RELATIVE_PART_REGEXP = /tps?:\/\/[^\/]+(.+)/;
  const SLASH_AT_STRING_START_REGEXP = /^\//;
  const PATH_SEGMENTS_DELIMITER = '/';

  return {
    source: url,
    protocol: a.protocol.replace(PROTOCOL_DELIMITER, DEFAULT_STRING),
    host: a.hostname,
    port: a.port,
    query: a.search,
    parameters: getParametersAndValues(a),
    file: (a.pathname.match(SYMBOLS_AFTER_LAST_SLASH_AT_STRING_END_REGEXP) || REGEXP_MATCH_STUB)[1],
    hash: a.hash.replace(URL_FRAGMENT_MARK, DEFAULT_STRING),
    path: a.pathname.replace(NOT_SLASH_AT_STRING_START_REGEXP, ORIGINAL_STRING_PREPENDED_BY_SLASH),
    relative: (a.href.match(URL_RELATIVE_PART_REGEXP) || REGEXP_MATCH_STUB)[1],
    segments: a.pathname.replace(SLASH_AT_STRING_START_REGEXP, DEFAULT_STRING).split(PATH_SEGMENTS_DELIMITER)
  };
}

There might also be several cases when adding slash is not possible. If you know some, please comment my answer.

Upvotes: 1

mandarin
mandarin

Reputation: 1386

Before finding this question and it's answers I created my own approach. I post it here as I don't see something similar.

function addSlashToUrl() {
    //If there is no trailing shash after the path in the url add it
    if (window.location.pathname.endsWith('/') === false) {
        var url = window.location.protocol + '//' + 
                window.location.host + 
                window.location.pathname + '/' + 
                window.location.search;

        window.history.replaceState(null, document.title, url);
    }
}

Upvotes: 2

Graham Hannington
Graham Hannington

Reputation: 1957

url += url.endsWith("/") ? "" : "/"

Upvotes: 66

bne
bne

Reputation: 261

I added to the regex solution to accommodate query strings:

http://jsfiddle.net/hRheW/8/

url.replace(/\/?(\?|#|$)/, '/$1')

Upvotes: 18

ManMohan Vyas
ManMohan Vyas

Reputation: 4062

var lastChar = url.substr(-1); // Selects the last character
if (lastChar != '/') {         // If the last character is not a slash
   url = url + '/';            // Append a slash to it.
}

The temporary variable name can be omitted, and directly embedded in the assertion:

if (url.substr(-1) != '/') url += '/';

Since the goal is changing the url with a one-liner, the following solution can also be used:

url = url.replace(/\/?$/, '/');
  • If the trailing slash exists, it is replaced with /.
  • If the trailing slash does not exist, a / is appended to the end (to be exact: The trailing anchor is replaced with /).

Upvotes: 128

honyovk
honyovk

Reputation: 2747

You can do something like:

var url = 'http://stackoverflow.com';

if (!url.match(/\/$/)) {
    url += '/';
}

Here's the proof: http://jsfiddle.net/matthewbj/FyLnH/

Upvotes: 4

Related Questions