11

I've been banging my head over this.

Using jquery or javascript, how can I toggle variables & values and then rebuild the query string? For example, my starting URL is:

http://example.com?color=red&size=small,medium,large&shape=round

Then, if the user clicks a button labeled "red", I want to end up with:

http://example.com?size=small,medium,large&shape=round //color is removed

Then, if the user clicks "red" again, I want to end up with:

http://example.com?size=small,medium,large&shape=round&color=red //color is added back

Then, if the user clicks a button labeled "medium", I want to end up with:

http://example.com?size=small,large&shape=round&color=red //medium is removed from list

Then, if the user clicks the labeled "medium" again, I want to end up with:

http://example.com?size=small,large,medium&shape=round&color=red //medium added back

It doesn't really matter what order the variable are in; I've just been tacking them to the end.

5
  • Have a look here stackoverflow.com/questions/2053157/… Commented Oct 29, 2011 at 6:46
  • Doesn't really handle multiple values in a single variable, but I can probably piece it together. Thanks for the find. Commented Oct 29, 2011 at 6:56
  • Sorry I did not have time to make a generic thing for you Commented Oct 29, 2011 at 10:10
  • this is very interested question, thank you! Commented Oct 29, 2011 at 12:39
  • Gotta say people... every one of these solutions rocked! Commented Nov 3, 2011 at 6:22

6 Answers 6

7
function toggle(url, key, val) {
    var out = [],
        upd = '',
        rm = "([&?])" + key + "=([^&]*?,)?" + val + "(,.*?)?(&.*?)?$",
        ad = key + "=",
        rmrplr = function(url, p1, p2, p3, p4) {
            if (p2) {
                if (p3) out.push(p1, key, '=', p2, p3.substr(1));
                else out.push(p1, key, '=', p2.substr(0, p2.length - 1));
            } else {
                if (p3) out.push(p1, key, '=', p3.substr(1));
                else out.push(p1);
            }
            if (p4) out.push(p4);
            return out.join('').replace(/([&?])&/, '$1').replace(/[&?]$/, ''); //<!2
        },
        adrplr = function(s) {
            return s + val + ',';
        };
    if ((upd = url.replace(new RegExp(rm), rmrplr)) != url) return upd;
    if ((upd = url.replace(new RegExp(ad), adrplr)) != url) return upd;
    return url + (/\?.+/.test(url) ? '&' : '?') + key + '=' + val; //<!1
}

params self described enough, hope this help.

!1: changed from ...? '&' : '' to ... ? '&' : '?'

!2: changed from .replace('?&','?')... to .replace(/([&?]&)/,'$1')...

http://jsfiddle.net/ycw7788/Abxj8/

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

3 Comments

This is really cool and very compact. But I think I'm hitting a bug. When sending key/value pair "color=red" into toggle(), then sending the same key/value pair back into toggle() again, then sending the same key/value pair in for the third time, I lose the "?".
One final bug here. Toggle on three different values from three different keys. Then, toggle off the second value. You end up with "&&" in the query string.
You should properly escape key and val prior using it. For example, when you use key=(foo, your code will throw an error, since ( has a special RegExp meaning.
5

I have written a function, which efficiently results in the expected behaviour, without use of any libraries or frameworks. A dynamic demo can be found at this fiddle: http://jsfiddle.net/w8D2G/1/

Documentation

Definitions:
The shown example values will be used at the Usage section, below
  -   Haystack - The string to search in (default = query string. e.g: ?size=small,medium)
  -   Needle - The key to search for. Example: size
  -   Value - The value to replace/add. Example: medium.

Usage (Example: input > output):

  1. qs_replace(needle, value)
    If value exists, remove: ?size=small,medium > ?size=small
    If value not exists, add: ?size=small > size=small,medium
  2. qs_replace(needle, options)     Object options. Recognised options:
    • find
      String. Returns true if the value exists, false otherwise.
    • add, remove or toggle
      String. Add/remove the given value to/from needle. If remove is used, and the value was the only value, needle is also removed. A value won't be added if it already exists.
    • ignorecase
      Ignore case while looking for the search terms (needle, add, remove or find).
    • separator
      Specify a separator to separate values of needle. Default to comma (,).

Note :   A different value for String haystack can also be defined, by adding it as a first argument: qs_replace(haystack, needle, value) or qs_replace(haystack, needle, options)

Code (examples at bottom). Fiddle: http://jsfiddle.net/w8D2G/1/:

function qs_replace(haystack, needle, options) {
    if(!haystack || !needle) return ""; // Without a haystack or needle.. Bye
    else if(typeof needle == "object") {
        options = needle;
        needle = haystack;
        haystack = location.search;
    } else if(typeof options == "undefined") {
        options = needle;
        needle = haystack;
        haystack = location.search;
    }

    if(typeof options == "string" && options != "") {
        options = {remove: options};
        var toggle = true;
    } else if(typeof options != "object" || options === null) {
        return haystack;
    } else {
        var toggle = !!options.toggle;
        if (toggle) {
            options.remove = options.toggle;
            options.toggle = void 0;
        }
    }

    var find = options.find,
        add = options.add,
        remove = options.remove || options.del, //declare remove
        sep = options.sep || options.separator || ",", //Commas, by default

        flags = (options.ignorecase ? "i" :"");

    needle = encodeURIComponent(needle); //URL-encoding
    var pattern = regexp_special_chars(needle);
    pattern = "([?&])(" + pattern + ")(=|&|$)([^&]*)(&|$)";
    pattern = new RegExp(pattern, flags);
    var subquery_match = haystack.match(pattern);

    var before = /\?/.test(haystack) ? "&" : "?"; //Use ? if not existent, otherwise &
    var re_sep = regexp_special_chars(sep);

    if (!add || find) { //add is not defined, or find is used
        var original_remove = remove;
        if (subquery_match) {
            remove = encodeURIComponent(remove);
            remove = regexp_special_chars(remove);
            remove = "(^|" + re_sep + ")(" + remove + ")(" + re_sep + "|$)";
            remove = new RegExp(remove, flags);
            var fail = subquery_match[4].match(remove);
        } else {
            var fail = false;
        }
        if (!add && !fail && toggle) add = original_remove;
    }
    if(find) return !!subquery_match || fail;
    if (add) { //add is a string, defined previously
        add = encodeURIComponent(add);
        if(subquery_match) {
            var re_add = regexp_special_chars(add);
            re_add = "(^|" + re_sep + ")(" + re_add + ")(?=" + re_sep + "|$)";
            re_add = new RegExp(re_add, flags);
            if (subquery_match && re_add.test(subquery_match[4])) {
                return haystack;
            }
            if (subquery_match[3] != "=") {
                subquery_match = "$1$2=" + add + "$4$5";
            } else {
                subquery_match = "$1$2=$4" + sep + add + "$5";
            }
            return haystack.replace(pattern, subquery_match);
        } else {
            return haystack + before + needle + "=" + add;
        }
    } else if(subquery_match){ // Remove part. We can only remove if a needle exist
        if(subquery_match[3] != "="){
            return haystack;
        } else {
            return haystack.replace(pattern, function(match, prefix, key, separator, value, trailing_sep){
                // The whole match, example: &foo=bar,doo
                // will be replaced by the return value of this function
                var newValue = value.replace(remove, function(m, pre, bye, post){
                    return pre == sep && post == sep ? sep : pre == "?" ? "?" : "";
                });
                if(newValue) { //If the value has any content
                    return prefix + key + separator + newValue + trailing_sep;
                } else {
                    return prefix == "?" ? "?" : trailing_sep; //No value, also remove needle
                }
            }); //End of haystack.replace
        } //End of else if
    } else {
        return haystack;
    }

    // Convert string to RegExp-safe string
    function regexp_special_chars(s){
        return s.replace(/([[^$.|?*+(){}\\])/g, '\\$1');
    }
}

Examples (Fiddle: http://jsfiddle.net/w8D2G/1/):

qs_replace('color', 'red'); //Toggle color=red
qs_replace('size', {add: 'medium'}); //Add `medium` if not exist to size
var starting_url = 'http://example.com?color=red&size=small,medium,large&shape=round'
starting_url = qs_replace(starting_url, 'color', 'red'); //Toggle red, thus remove
starting_url = qs_replace(starting_url, 'color', 'red'); //Toggle red, so add it
alert(starting_url);

Comments

2

This is the solution for your task: http://jsfiddle.net/mikhailov/QpjZ3/12/

var url = 'http://example.com?size=small,medium,large&shape=round';
var params = $.deparam.querystring(url);
var paramsResult = {};

var click1 = { size: 'small' };
var click2 = { size: 'xlarge' };
var click3 = { shape: 'round' };
var click4 = { shape: 'square' };

var clickNow = click4;

for (i in params) {
    var clickKey = _.keys(clickNow)[0];
    var clickVal = _.values(clickNow)[0];

    if (i == clickKey) {
       var ar = params[i].split(',');

       if (_.include(ar, clickVal)) {
           var newAr = _.difference(ar, [clickVal]);
       } else {
           var newAr = ar;
           newAr.push(clickVal);
       }
       paramsResult[i] = newAr.join(',');
    } else {
       paramsResult[i] = params[i];
    }

}

alert($.param(paramsResult)) // results see below

Init params string

{ size="small, medium,large", shape="round"} // size=small,medium,large&shape=round

Results

{ size="small"}   => { size="medium,large", shape="round"} //size=medium%2Clarge&shape=round

{ size="xlarge"}  => { size="small,medium,large,xlarge", shape="round"} // size=small%2Cmedium%2Clarge%2Cxlarge&shape=round

{ shape="round"}  => { size="small,medium,large", shape=""} //size=small%2Cmedium%2Clarge&shape=

{ shape="square"} => { size="small,medium,large", shape="round,square"} //size=small%2Cmedium%2Clarge&shape=round%2Csquare

4 Comments

@joeyr did you have a chance to try this out?
Yes - first test left a key with an empty value on the URL. Need to test more.
Can you copy-paste your input data and explain what doesn't work please?
@joeyr, this is a cleanest code you can find among answers and it works, you need to double check jsFiddle
2
+100

productOptions is the only thing you need to modify here to list all the available options and their default state. You only need to use the public API function toggleOption() to toggle an option.

(function(){
    //Just keep an object with all the options with flags if they are enabled or disabled:

    var productOptions = {

        color: {
            "red": true,
            "blue": true,
            "green": false
        },

        size: {
            "small": true,
            "medium": true,
            "large": true
        },

        shape: {
            "round": true
        }
    };


    //After this constructing query becomes pretty simple even without framework functions:

    function constructQuery(){
    var key, opts, qs = [], enc = encodeURIComponent, opt,
        optAr, i;

        for( key in productOptions ) {
        opts = productOptions[key];
        optAr = [];
            for( i in opts ) {
                if( opts[i] ) {
                optAr.push( i );
                }
            }

            if( !optAr.length ) {
            continue;
            }

        qs.push( enc( key ) + "=" + enc( optAr.join( "," ) ) );
        }

    return "?"+qs.join( "&" );
    };

    //To toggle a value and construct the new query, pass what you want to toggle to this function:

    function toggleOption( optionType, option ) {

        if( optionType in productOptions && option in productOptions[optionType] ) {
        productOptions[optionType][option] = !productOptions[optionType][option];
        }

    return constructQuery();
    }

window.toggleOption = toggleOption;
})()

Example use:

// "%2C" = url encoded version of ","

toggleOption(); //Default query returned:
"?color=red%2Cblue&size=small%2Cmedium%2Clarge&shape=round"

toggleOption( "color", "red" ); //Red color removed:
"?color=blue&size=small%2Cmedium%2Clarge&shape=round"

toggleOption( "color", "blue" ); //Blue color removed, no color options so color doesn't show up at all:
"?size=small%2Cmedium%2Clarge&shape=round"

toggleOption( "color", "blue" ); //Blue color enabled again:
"?color=blue&size=small%2Cmedium%2Clarge&shape=round"

toggleOption( "shape", "round" ); //The only shape option removed
"?color=blue&size=small%2Cmedium%2Clarge"

2 Comments

This is awesome answer! Can you provide jfFiddle please? It it works, you will win a bounty, here is end of it in 20hours
Here is a jsfiddle jsfiddle.net/hSp4J. It has the urldecoded version in the html and raw results in console log
0

I have tried this and this may give the desire result

<script>
var url='http://example.com?color=red&size=small,medium,large&shape=round';
var mySplitResult = url.split("?");
var domain=mySplitResult[0];
var qstring=mySplitResult[1];
var proparr=new Array();
var valarr=new Array();
var mySplitArr = qstring.split("&");
for (i=0;i<mySplitArr.length;i++){ 
    var temp = mySplitArr[i].split("=");
    proparr[i]=temp[0];
    valarr[i]=temp[1].split(",");   
 }
function toggle(property,value)
{
    var index;
    var yes=0;
    for (i=0;i<proparr.length;i++){ 
    if(proparr[i]==property)
    index=i;
    }
    if(index==undefined){
    proparr[i]=property;
    index=i;
    valarr[index]=new Array();  
    }   
    for (i=0;i<valarr[index].length;i++){ 
        if(valarr[index][i]==value){
        valarr[index].splice(i,1);
        yes=1;
        }   
    }
    if(!yes)
    {
    valarr[index][i]=value;
    }
    var furl=domain +'?';
    var test=new Array();
    for(i=0;i<proparr.length;i++)
    {
    if(valarr[i].length)
    {
    test[i]=valarr[i].join(",");
    furl +=proparr[i]+"="+test[i]+"&";  
    }   
    }
    furl=furl.substr(0,furl.length-1)
    alert(furl);
}
</script>


<div>
<input id="color" type="button" value="Toggle Red" onclick="toggle('color','red')"/>
<input id="shape" type="button" value="Toggle shape" onclick="toggle('shape','round')"/>
<input id="size" type="button" value="Toggle Small" onclick="toggle('size','small')"/>
<input id="size" type="button" value="Toggle large" onclick="toggle('size','large')"/>
<input id="size" type="button" value="Toggle medium" onclick="toggle('size','medium')"/>
<input id="size" type="button" value="Toggle new" onclick="toggle('new','yes')"/>
</div>

Comments

0

You can toggle a query param easily like this:

const searchParams = new URLSearchParams(window.location.search);

if (searchParams.has('color')) {
    searchParams.delete('color');
} else {
    searchParams.set('color', 'red');
}

window.location.search = searchParams.toString();

Working example:

const currentURLDebug = document.getElementById('current-url-debug');
const toggleColorButton = document.getElementById('toggle-color-button');

toggleColorButton.addEventListener('click', () => {
    const currentURL = new URL(currentURLDebug.textContent);
    const searchParams = new URLSearchParams(currentURL.search);

    if (searchParams.has('color')) {
        searchParams.delete('color');
    } else {
        searchParams.set('color', 'red');
    }
    currentURL.search = searchParams.toString();
    currentURLDebug.textContent = currentURL;
}, false);
<span id="current-url-debug">https://example.com</span>
<br>
<button id="toggle-color-button">Toggle color</button>

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.