3

I have the following JavaScript which build url paramters based on users input:-

$(document).ready(function(){

    $('#button').click(function(e) {  
        var count=1;
        var s="";

        var inputvalue = $("#journal").val();
        var inputvalue2 = $("#keywords").val();
        var inputvalue3 = $("#datepub").val();
        var inputvalue4 = $("#title").val();
        var inputvalue5 = $("#localcurrency").val();
        var inputvalue6 = $("#locations").val();
        var inputvalue7 = $("#dropdown1").val();
        var inputvalue8 = $("#dropdown2").val();

        if(inputvalue!=null && inputvalue!="")
        {
        s = s+ "FilterField"+count+"=Journal&FilterValue"+count+"="+inputvalue+"&";
        count++;
        }
        if(inputvalue2!=null && inputvalue2!="")
        {
        s = s+ "FilterField"+count+"=KeyWords&FilterValue"+count+"="+inputvalue2+"&";
        count++;
        }
        if(inputvalue3!=null && inputvalue3!="")
        {
        s = s+ "FilterField"+count+"=datepub&FilterValue"+count+"="+inputvalue3+"&";
        count++;
        }
        if(inputvalue4!=null && inputvalue4!="")
        {
        s = s+ "FilterField"+count+"=Title&FilterValue"+count+"="+inputvalue4+"&";
        count++;
        }
        if(inputvalue5!=null && inputvalue5!="")
        {
        s = s+ "FilterField"+count+"=localcurrency&FilterValue"+count+"="+inputvalue5+"&";
        count++;
        }
        if(inputvalue6!=null && inputvalue6!="")
        {
        s = s+ "FilterField"+count+"=locations&FilterValue"+count+"="+inputvalue6+"&";
        count++;
        }
        if(inputvalue7!=null && inputvalue7!="")
        {
        s = s+ "FilterField"+count+"=dropdown1&FilterValue"+count+"="+inputvalue7+"&";
        count++;
        }
        if(inputvalue8!=null && inputvalue8!="")
        {
        s = s+ "FilterField"+count+"=dropdown2&FilterValue"+count+"="+inputvalue8+"&";
        count++;
        }

        window.location.replace("/teamsites/Bib%20Test/Forms/search.aspx?"+s);

    });
});
</script> 

now the above script will generate URLs such as

http://***/teamsites/Bib%20Test/Forms/search.aspx?FilterField1=Journal&FilterValue1=123

http://***/teamsites/Bib%20Test/Forms/search.aspx?FilterField1=Journal&FilterValue1=123&FilterField2=localcurrency&FilterValue2=USD&

and thing were working well, till i tried passing a search parameter which contain &. for example i wanted to search for a record which have their journal = General&Procedure, so using my above code, the URL will be as follow:-

http://***/teamsites/Bib%20Test/Forms/search.aspx?FilterField1=Journal&FilterValue1=General&Procedure&

and i did not get any result,, as the application assume that the Procudure is a parameter and not part of the FilterValue1.. now to fix this specific problem, i define to build the URL parameters with encodeURIComponent() function as follow:-

var inputvalue = encodeURIComponent($("#journal").val());
var inputvalue2 = encodeURIComponent($("#keywords").val());
var inputvalue3 = encodeURIComponent($("#datepub").val());
var inputvalue4 = encodeURIComponent($("#title").val());
var inputvalue5 = encodeURIComponent($("#localcurrency").val());
var inputvalue6 = encodeURIComponent($("#locations").val());
var inputvalue7 = encodeURIComponent($("#dropdown1").val());
var inputvalue8 = encodeURIComponent($("#dropdown2").val());

now the generated URL will be as follow:-

http://***teamsites/Bib%20Test/Forms/search.aspx?FilterField1=Journal&FilterValue1=General%26Procedure

and i got the expected results.. but not sure if using encodeURIComponent() to only encode the parameter values is a valid fix,, as seems i will be encoding the & if it is part of the query string parameter,, but still the url contain non-encoded & which separate the url parameters .. now the result i got from the last url is correct.. but not sure if i am doing things correctly ? and is there a built-in function to do this work for me ?? Thanks

John John
  • 1
  • 72
  • 238
  • 501
  • Please use of [Array](https://www.w3schools.com/jsref/jsref_obj_array.asp) – Orelsanpls Oct 27 '17 at 15:34
  • @GrégoryNEUT what do you mean? and how i can use it inside case? – John John Oct 27 '17 at 15:35
  • 1
    jQuery.param() would make your life easier – epascarello Oct 27 '17 at 16:12
  • @epascarello can you please advice more on how to use `jQuery.param()` in my case? second question, is the way i am doing things by encoding the parameter values using `encodeURIComponent()` as valid way ? – John John Oct 29 '17 at 00:21
  • *but not sure if using encodeURIComponent() to only encode the parameter values is a valid fix* This is not only valid and correct, it is actually the **only fix for supporting special characters** in values which have special meaning for a URL. – gurvinder372 Oct 31 '17 at 09:39
  • How about making your Method more generic. More like in this question: https://stackoverflow.com/a/111545/5504438 – maracuja-juice Oct 31 '17 at 21:50

5 Answers5

3

Here are sources for URL syntax:

You will notice that the exact content of the query component is not standardized. Its simple definition is:

The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI.

However, the de-facto standard is to use ampersand (&) character as delimiter. With this convention, anytime this character also appears in your data and is not meant to be a delimiter, you have to "percent-encode" it, as per the standard as well:

A percent-encoding mechanism is used to represent a data octet in a component when that octet's corresponding character is outside the allowed set or is being used as a delimiter of, or within, the component.

You will easily understand that other special characters, like =, % and # must also be percent-encoded, should they appear in your data. There is no harm in encoding even more special characters as well.

Therefore if you follow this convention, your query component should be of the form:

?field1=value1&field2=value2

with each field and value being percent-encoded. In JavaScript, you can indeed conveniently use the encodeURIComponent function. Do not forget to encode the fields as well!

Furthermore, as your use case is very common, there are plenty libraries available that can handle such conversion for you, e.g. URI.js.

But since you mention using jQuery, you can conveniently use jQuery.param to do the conversion:

Create a serialized representation of an array, a plain object, or a jQuery object suitable for use in a URL query string or Ajax request. In case a jQuery object is passed, it should contain input elements with name/value properties.

$(document).ready(function() {
  $('#button').click(retrieveInputsValues);

  retrieveInputsValues();
});

function retrieveInputsValues() {
  var inputIds = [
    'Journal',
    'KeyWords',
    'datepub',
    'Title',
    'localcurrency',
    'locations',
    'dropdown1',
    'dropdown2'
  ];
  var obj = {};
  var count = 1;
  var value;

  for (var i = 0; i < inputIds.length; i += 1) {
    value = $('#' + inputIds[i].toLowerCase()).val();
    if (value !== null && value !== '') {
      obj['FilterField' + count] = inputIds[i];
      obj['FilterValue' + count] = value;
      count += 1;
    }
  }

  console.log($.param(obj));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
Journal
<input type="text" id="journal" value="test & ampersand, comma, % percent, = equal and space" />
<br />keywords <input type="text" id="keywords" />

<br />datepub
<select id="datepub">
  <option value=""></option>
  <option value="1950">1950</option>
  <option value="2010">2010</option>
  <option value="2017" selected>2017</option>
  <option value="audi">Audi</option>
</select>
<br />title
<select id="title">
  <option value=""></option>
  <option value="TestDoc">test doc</option>
  <option value="t">t</option>
</select>
<br />localcurrency
<select id="localcurrency">
  <option value=""></option>
  <option value="USD">USD</option>
</select>
<br />locations
<select id="locations">
  <option value=""></option>
  <option value="US">US</option>
  <option value="UK">UK</option>
</select>
<br />dropdown1
<select id="dropdown1">
  <option value=""></option>
  <option value="a">a</option>
  <option value="b">b</option>
</select>
<br />dropdown2
<select id="dropdown2">
  <option value=""></option>
  <option value="aa">aa</option>
  <option value="bb">bb</option>
  <option value="cc">cc</option>
  <option value="dd">dd</option>
</select>
<br />
<button type="button" id="button">search</button>
<!-- re-used from https://stackoverflow.com/a/47008115/5108796 -->

BTW, usually there is no need to pass the field names as values, just "field=value" is used.

But you may have specific use case for your back-end processing?

Community
  • 1
  • 1
ghybs
  • 47,565
  • 6
  • 74
  • 99
  • thanks for the great reply.. so now i need to `encodeURIComponent` the parameters value and name ONLY,, while i should leave the `&` ,`=` un-encoded when they are used to build the url... second point, now instead of manually using `encodeURIComponent` as in my code. i can build the parameters using array and use the `$.param(obj)` to build the encoded url??? but my original code is valid,,, although it can be improved for better understandability and maintenalitiy ... is this correct? – John John Oct 30 '17 at 13:35
  • 1
    Yep, sounds like. Read the manual for `jQuery.param`, you should be able to use it easily. – ghybs Oct 30 '17 at 13:37
  • @Gyhbs but the problem i am facing is that in-case inside the advance search i leave a free text field or a drop-down field empty, then they will be part of the url query with empty value,, which will cause problems.. while inside my original code i will not include the field in the query unless it contain data... so can i got this fixed inside ur code? for example i define to ONLY search the datepub=1950 but i got this query – John John Oct 30 '17 at 16:28
  • `search.aspx?FilterField1=Journal&FilterValue1=&FilterField2=KeyWords&FilterValue2=&FilterField3=datepub&FilterValue3=1950&FilterField4=Title&FilterValue4=&FilterField5=localcurrency&FilterValue5=&FilterField6=locations&FilterValue6=&FilterField7=dropdown1&FilterValue7=&FilterField8=dropdown2&FilterValue8=` – John John Oct 30 '17 at 16:28
  • 1
    Not sure I got exactly what you mean, but if you want not to include fields which have falsy value, that is trivial to do. I will edit the above answer. – ghybs Oct 30 '17 at 16:35
2

Extending my comment as an answer.

Using encodeURIComponent is not only valid and correct, it is actually the only fix for supporting special characters in values in URL which have special meaning for a URL.

Encoding the values in URL component is important for prevent XSS attacks as well. Have a look here

URL-escaping is susceptible to double-escaping, meaning you must URL-escape its parts exactly once. It is best to perform the URL-escaping at the time the URL is being assembled.

However, you can improve your code in the following manner

var inputs = [ "#journal", "#keywords", "#datepub", "#title", "#localcurrency", "#locations", "#dropdown1", "#dropdown2" ];
$(document).ready(function(){
    $('#button').click(function(e) {  
        var count =  1;
        var searchParams = inputs.filter( function( id ){
            return $( "#" + id ).val().trim().length > 0;
        }).map( function( id ){
            var value = encodeURIComponent( $( "#" + id ).val().trim() );
            return "FilterField" + (count) + "=" + id + "&FilterValue" + (count++) + "=" + value;
        }).join( "&" );
        window.location.replace("/teamsites/Bib%20Test/Forms/search.aspx?"+ searchParams );
    });
});

Alternatively, you can also use URL (though not supported in IE)

var inputs = [ "#journal", "#keywords", "#datepub", "#title", "#localcurrency", "#locations", "#dropdown1", "#dropdown2" ];
$(document).ready(function(){
    $('#button').click(function(e) {  
        var count =  1;
        var url = new URL( "/teamsites/Bib%20Test/Forms/search.aspx?", window.location.origin );        
        inputs.forEach( function( id ){
            var value = encodeURIComponent( $( "#" + id ).val().trim() );
            if ( value.length >  0 )
            {
                url.searchParams.append( "FilterField" + count, id );
                url.searchParams.append( "FilterValue" + (count++), value );
            }
        });
        window.location.replace( url.href );
    });
});

As you can see that in this approach as well, you will have to use encodeURIcomponent since as per spec

The append(name, value) method, when invoked, must run these steps:

Append a new name-value pair whose name is name and value is value, to list.

Run the update steps.

there is no guarantee that encoding will be done. So, the explicit encoding necessary!!.

gurvinder372
  • 66,980
  • 10
  • 72
  • 94
  • now instead of manually using `encodeURIcomponent` i can build an array of all the parameters then i can encode them using `jQuery.param` is this correct ? or you prefer to do the `encodeURIcomponent ` manually ?? – John John Nov 01 '17 at 01:20
  • 1
    @johnG yes off course, jquery param uses encodeuricomponent internally. If you are already using jquery then go for it. – gurvinder372 Nov 01 '17 at 02:52
1

/!\ THIS IS NOT AN ANSWER

In relation with comments

const [
   $("#journal").val(),
   $("#keywords").val(),
   $("#datepub").val(),
   $("#title").val(),
   // ...
].forEach((x) => {
   if (x !== null && x !== '') {
     s += ...;

     count += 1;
   }
});
Orelsanpls
  • 22,456
  • 6
  • 42
  • 69
0

I use encodeUriComponent for this.

url += "&filter=" + encodeURIComponent(filter);

You want '&' inside the parameter value to be encoded, so you use 'encodeURIComponent' on the value of the parameter, but you don't want to encode the stuff between parameters.

TKoL
  • 13,158
  • 3
  • 39
  • 73
  • so it is the same thing i am doing ??? and do we need to encode the `&` which separate the parameters ?? can you answer my question instead of providing general answer... thanks – John John Oct 27 '17 at 15:34
  • 1
    To separate parameters, you need the & unencoded. You seem to understand correctly. You want '&' inside the parameter value to be encoded, so you use 'encodeURIComponent' on the value of the parameter, but you don't want to encode the stuff between parameters. – TKoL Oct 27 '17 at 15:37
  • so the way i fix the problem,, is the correct way ? – John John Oct 27 '17 at 15:44
  • 1
    Yes, you encode only the parameter values with `encodeURIComponent` – TKoL Oct 27 '17 at 15:52
  • so can i say that we usually encode the preserved values when they are not used as intended.. so if i want to use `&` as part of a parameter value i should encode it ... but if i want to use it to separate the parameters i keep it as is !! is there any documentation which describe this? – John John Oct 27 '17 at 15:55
  • @johnG honestly I struggled finding an answer to this very question before trying to figure it out myself, and I'm *not* confident that this answer is the best answer. You're right, the documentation about how to use this function isn't detailed enough. But this is my best guess. – TKoL Oct 27 '17 at 15:56
0

Use this if you are not concerned about Internet Explorer or Edge.

I would recommend to use browser's URL API instead. It is stable and is available in most of the modern browsers to deal with URL specific work natively.

Your code can be changed as follows to use this API. It automatically encodes all the required parameters as per the specs. You don't need to deal with the query parameters manually.

$(document).ready(function() {

  $('#button').click(function(e) {
    var count = 1;
    var s = "";
    var url = new URL("http://yourhost.com/teamsites/Bib%20Test/Forms/search.aspx");

    var inputvalue = $("#journal").val();
    var inputvalue2 = $("#keywords").val();
    var inputvalue3 = $("#datepub").val();
    var inputvalue4 = $("#title").val();
    var inputvalue5 = $("#localcurrency").val();
    var inputvalue6 = $("#locations").val();
    var inputvalue7 = $("#dropdown1").val();
    var inputvalue8 = $("#dropdown2").val();

    if (inputvalue != null && inputvalue != "") {
      url.searchParams.set("FilterField" + count, "Journal");
      url.searchParams.set("FilterValue" + count, inputvalue);
      count++;
    }

    if (inputvalue2 != null && inputvalue2 != "") {
      url.searchParams.set("FilterField" + count, "KeyWords");
      url.searchParams.set("FilterValue" + count, inputvalue2);
      count++;
    }

    if (inputvalue3 != null && inputvalue3 != "") {
      url.searchParams.set("FilterField" + count, "datepub");
      url.searchParams.set("FilterValue" + count, inputvalue3);
      count++;
    }

    if (inputvalue4 != null && inputvalue4 != "") {
      url.searchParams.set("FilterField" + count, "Title");
      url.searchParams.set("FilterValue" + count, inputvalue4);
      count++;
    }

    if (inputvalue5 != null && inputvalue5 != "") {
      url.searchParams.set("FilterField" + count, "localcurrency");
      url.searchParams.set("FilterValue" + count, inputvalue5);
      count++;
    }

    if (inputvalue6 != null && inputvalue6 != "") {
      url.searchParams.set("FilterField" + count, "locations");
      url.searchParams.set("FilterValue" + count, inputvalue6);
      count++;
    }

    if (inputvalue7 != null && inputvalue7 != "") {
      url.searchParams.set("FilterField" + count, "dropdown1");
      url.searchParams.set("FilterValue" + count, inputvalue7);
      count++;
    }

    if (inputvalue8 != null && inputvalue8 != "") {
      url.searchParams.set("FilterField" + count, "dropdown2");
      url.searchParams.set("FilterValue" + count, inputvalue8);
      count++;
    }

    window.location.replace(url.href);

  });
});

In addition to it, I recommend to incorporate the suggestions from @GrégoryNEUT, as it makes the code concise and easy to read.

31piy
  • 23,323
  • 6
  • 47
  • 67