- PVSM.RU - https://www.pvsm.ru -
Доброго времени суток, уважаемые читатели!
Возникла передо мной сегодня задача генерации GET-параметров и всего URL в целом, на стороне клиента, прям вот щас, без возможности «поговорить» с сервером. Сразу оговорюсь, про этот [1] пост я узнал вот прям перед написанием данной статьи ибо сначала закончил писать, а потом уже прибег к поиску, да и пост тот — не со всем про то же самое, что у меня.
Итак, к делу.
Проблемы — те же что и в посте, который я привел выше:
window.location
для «приготовления» URL;window.location
в силу политики безопасности браузеров;Задачи которые я поставил перед собой:
Писал я на чистейшем JavaScript, причем без использования prototype.__defineGetter__
или prototype.__defineSetter__
в угоду кроссбраузерности ибо IE < 9 такого не умеет. Более подробно про getters/setters написано в этом [2] посте.
Для тех кому интересно — сядем разберем, а кому надо готовое решение — милости прошу в конец поста, ссылки на скачивание — там.
Приступим! Раньше сядем — раньше выйдем.
var URL = function( param, param2 ){
param = param || false;
param2 = ( param2 === false ) ? false : true;
this.urlEncode = param2;
this.data = { scheme: false, user: false, pass: false, host: false, port: false, path: false, query: false, params: {}, fragment: false };
if( typeof(param) == 'string' ){
this.url = param;
this.parse();
} else if ( typeof(param) == 'object' ){
for(var key in param){
if( this.data.hasOwnProperty( key ) ){
if( param[ key ] || ( key == 'params' && typeof(param.params) == 'object' ) )
this.data[ key ] = param[ key ];
}
}
this.update();
}
}
parse_url()
из PHP, мне так просто удобнее.
Надо парсить уже имеющийся URL, делать мы это будем при помощи RegExp
. Нет, можно конечно все обрабатывать при помощи str.split()
, но это, как мне кажется — особый вид фетишизма.
regExp = /^(?:([a-z0-9_-.]+)://)*(?:([a-z0-9_-.]+)(?::)*([a-z0-9_-.]+)*@)*([a-z0-9][a-z0-9_-.]+)(?::([d]+))*(?:/([^?#]*))*(?:?([^?#]*))*(?:#([^?#]*))*/gi;
(?:([a-z0-9_-.]+)://)*
— SCHEME, если верить википедии [3], то схема имеет вид ххх://
причем, там могут быть и -
и _
. В угоду универсальности, установлен * т.е. схема может быть и не указана.(?:([a-z0-9_-.]+)(?::)*([a-z0-9_-.]+)*@)*
— USER:PASSWORD, пароля без юзернейма не бывает, а юзернейм без пароля бывает.([a-z0-9][a-z0-9_-.]+)
— HOST, насколько я знаю, начинаться доменное имя может только с буквы/цифры, а дальше уже могут идти и — и _ и. Более того, не бывает доменных имен короче 6 символов, но ведь ссылки то бывают и внутрисетевые, где хостнеймами как хочешь так и рулишь, та что сойдет и 1+ символ.(?::([d]+))*
— PORT, данный параметр опционален,: а далее цифры(?:/([^?#]*))*
— PATH, путь до файла, в общем-то, по-идее, это любое количество любых символов, но, отсечем? и # дабы не спарсить в путь GET-параметры или фрагментарный указатель. Путь может быть и неуказан.(?:?([^?#]*))*
— QUERY, набор GET-параметров, пар ключ=значение. Так же может быть и не указан.(?:#([^?#]*))*
— FRAGMENT, фрагментарный указатель. Если кто не знает — то /index.html#fragment
дает команду браузеру проскроллить к DOM-элементу с id="fragment"
Работать, ясное дело, будет на всех языках, понимающих RegExp. Пользуйтесь, не стесняйтесь.
parse: function(){
this.res = /^(?:([a-z0-9_-.]+)://)*(?:([a-z0-9_-.]+)(?::)*([a-z0-9_-.]+)*@)*([a-z0-9][a-z0-9_-.]+)(?::([d]+))*(?:/([^?#]*))*(?:?([^?#]*))*(?:#([^?#]*))*/gi.exec( this.url );
this.data.scheme = this.res[ 1 ] || false;
this.data.user = this.res[ 2 ] || false;
this.data.pass = this.res[ 3 ] || false;
this.data.host = this.res[ 4 ] || false;
this.data.port = this.res[ 5 ] || false;
this.data.path = this.res[ 6 ] || false;
this.data.query = this.res[ 7 ] || false;
this.data.fragment = this.res[ 8 ] || false;
if( this.data.query ){
this.parts = this.data.query.split( '&' );
for( var i = 0; i < this.parts.length; i++ ){
param = this.parts[ i ].split( '=' );
this.data.params[ param[ 0 ] ] = decodeURIComponent( param[ 1 ] );
}
}
delete this.res;
delete this.parts;
}
Тут ничего ничего сложного: разбиение по указанному выше regExp
и сохранение данных в хеш this.data
Разве что, я упоминал ранее — необходима удобная работа с GET-параметрами урла, а посему разбиваем query при помощи split ( split() в данном случае «дешевле» чем regExp
) и сохраняем это в тот же пресловутый хэш. Стоит отметить использование decodeURIComponent, ведь GET-параметры могут быть urlencoded.
Для удобной работы с чтением/изменением параметров я решил выбрать JS way геттеры и сеттеры. T.e. метод по названию свойства и если метод вызывается с указанием параметра — это setter, если без параметра — это getter.
Объявлять я их буду через URL.prototype = { }
дабы не плодить в памяти избыточные экземпляры метода.
В пример приведу один метод, в силу того что они похожи:
scheme: function( param ){
if( typeof( param ) != 'undefined' ){
this.data.scheme = param;
return this.update();
} else {
return this.data.scheme ? this.data.scheme : false;
}
}
Замечу, что в случае изменения значения возвращается не String
, а Object
сделано это для того, чтобы можно было писать цепочки сеттеров:
var url = new URL();
url.scheme('https').host('example.com').path('index.php').params({'p1':"v1", 'p2':"в2"}).url;
// вернет: https://example.com/index.php?p1=v1&p2=%D0%B22
Отдельно остановимся на геттер/сеттере для свойства params
params: function( param1, param2 ){
if( typeof( param1 ) != 'undefined' ){
if( typeof( param1 ) == 'string' ){
if( typeof( param2 ) != 'undefined' && ( param2 == '' || param2 === false ) ){
if( this.data.params.hasOwnProperty( param1 ) ){
delete this.data.params[ param1 ];
}
} else if( typeof( param2 ) != 'undefined' ){
this.data.params[ param1 ] = param2;
} else{
return this.data.params[ param1 ] ? this.data.params[ param1 ] : false;
}
} else if( typeof( param1 ) == 'object' ){
for( var key in param1 ){
if( typeof( param1[ key ] ) != 'undefined' && ( param1[ key ] == '' || param1[ key ] === false ) ){
if( this.data.params.hasOwnProperty( key ) )
delete this.data.params[ key ];
} else{
this.data.params[ key ] = param1[ key ];
}
}
}
return this.update();
} else {
return this.data.params ? this.data.params : false;
}
}
Как видим — оба параметра опциональные.
И как я говорил — я ставил перед собой целью — удобство работы с GET-параметрами, а значит мы должны уметь:
как отдельно взятый параметр так и группы параметров.
Соответственно синтаксис будет таков:
param1
и значением param2
false
— указанный GET-параметр удаляется
Как вы заметили, в геттерах вызывается this.update()
выполняет он 2 функции:
query
при манипуляциях с GET-параметрами update: function(){
this.data.query = '';
for( var key in this.data.params ){
this.data.query += this.urlEncode ? key+'='+encodeURIComponent( this.data.params[ key ] )+'&' : key+'='+this.data.params[ key ]+'&';
}
if( this.data.query )
this.data.query = this.data.query.slice( 0, -1 );
this.url = '';
this.url += this.data.scheme ? this.data.scheme+'://' : '';
this.url += this.data.user ? this.data.user+':' : '';
this.url += this.data.pass ? this.data.pass+'@' : '';
this.url += this.data.host ? this.data.host+'/' : '';
this.url += this.data.path ? this.data.path : '';
this.url += this.data.query ? '?'+this.data.query : '';
this.url += this.data.fragment ? '#'+this.data.fragment : '';
return this;
}
Стоит отметить, что при сборке GET-параметров, значения параметров преобразуются в escape-последовательность.
Во-первых: это правильно.
Во-вторых: если мы GET-параметром передаем данные вводимые пользователем, то вставленный юзером амперсанд разрушит последовательность ключ-значение и все покатится в тартарары.
Ну, а если уж, прям кровь из носу, вам не нужна urlencoded строка — у вас два варианта:
Передаем вторым параметром в конструкторе false
URL.urlEncode=false;
URL.update();
test = new URL({"path":"index.php", "params":{"param1":"value1", "param2":"значение параметра&"}}, false);
test.url;
//index.php?param1=value1¶m2=значение параметра&
test2 = new URL({"path":"index.php", "params":{"param1":"value1", "param2":"значение параметра&"}});
test2.url;
//index.php?param1=value1¶m2=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%B0%26
test2.urlEncode=false;
test2.update().url;
//index.php?param1=value1¶m2=значение параметра&
Ну и чтобы было удобно — метод для перехода по сгенерированной ссылке:
go: function(){
if(!this.data.scheme && this.data.host)
this.data.scheme = 'http';
window.location.href = this.update().url;
}
Как видно: если не указана схема, но указан хост — автоматически подставляется схема http
как самая распространенная.
Далее происходит обновление ссылки и переход по оной.
По идее, на этом можно было бы закончить. Но, мне показалось что было бы удобно работать прямо со строковыми переменными без явного создания экземпляра объекта (как бы странно это не звучало, но, в JS нет классов, как таковых).
Как обычно приведу пример одного метода:
String.prototype.scheme = function( param ){
var url = new URL( this.valueOf() );
if( typeof( param ) != 'undefined' ){
url.scheme( param );
result = url.url;
} else{
result = url.scheme();
}
delete url;
return result;
}
В общем-то код просто передает параметры в соответствующий метод объекта URL.
Но некоторым может показаться странным тот момент, что я каждый вызов по-новой создаю и удаляю объекты URL и делаю только одно действие, причем это действие не меняет значения переменной над которой оно производится.
Вот тут то и кроется самое главное неудобство объекта String
, нельзя менять значение существующей переменной. С ней вообще ничего нельзя сделать, ВСЕГДА создается новая переменная. А по-этому каждый раз создается новый объект и возвращается переменная типа String
.
Цепочки конечно же поддерживаются:
url = 'example.com';
url.scheme('https').path('index.php').params({'p1':"v1", 'p2':"в2"});
// вернет: https://example.com/index.php?p1=v1&p2=%D0%B22
Если предыдущий вариант, скажем так, был «красив»в использовании, то данный вариант, будет лаконичен. как с точки зрения кода, так и с точки зрения использования.
Так вот, getter/setter в данном случае будет один на всё, ну то есть совсем.
val: function( key, param, param2 ){
if( this.data.hasOwnProperty( key ) ){
if( typeof( param ) == 'undefined' ){
return this.data[ key ] ? this.data[ key ] : false;
} else if( typeof( param ) != 'undefined' ){
if( key == 'params' ){
if( typeof( param ) == 'string' ){
if( typeof( param2 ) != 'undefined' ){
this.data[ key ][ param ] = param2;
} else{
return this.data[ key ][ param ] ? this.data[ key ][ param ] : false;
}
} else if( typeof( param ) == 'object' ){
for( var keys in param ){
if( typeof( param[ keys ] ) != 'undefined' && ( param[ keys ] == '' || param[ keys ] === false ) ){
if( this.data[ key ].hasOwnProperty( keys ) ){
delete this.data[ key ][ keys ];
}
} else{
this.data[ key ][ keys ] = param[ keys ];
}
}
}
} else{
this.data[ key ] = param;
}
return this.update();
}
} else
return 'undefined';
}
Идентичная ситуация и с расширением объекта String
, только кода поменьше, т.к. этот метод всего лишь транспортирует параметры в URL.val();
Итак, на выходе мы имеем либу, дающую нам возможность адекватно работать с URL, причем не просто парсить, но и менять отдельные участи URL. Это уже не говоря о весьма удобном, на мой взгляд, инструменте для работы с GET-параметрами.
Плюсы:
Минусы:
Плюсы:
Минусы:
Скачать исходники обоих вариантов можно тут: [ Вариант 1 [4] || Вариант 2 [5] ]. смысла выкладывать на гитхаб не вижу, ибо всего 1 файл.
Поддержка:
JavaSript
, ибо плагин написан на чистом, нативном JS, без использования magic
функций, которые не поддерживаются старыми браузерами.А за сим — откланяюсь, искренне надеюсь что мой пост принесет кому-то пользу.
Всем хорошего кода, больше сна и чтобы IE не портил жизнь.
Автор: xobotyi
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/66685
Ссылки в тексте:
[1] этот : http://habrahabr.ru/post/65407/
[2] этом: http://habrahabr.ru/post/66242/
[3] википедии: http://en.wikipedia.org/wiki/URI_scheme
[4] Вариант 1: https://yadi.sk/d/kLV-QvlFZ3xcA
[5] Вариант 2: https://yadi.sk/d/FstYPSNHZ3xYi
[6] Источник: http://habrahabr.ru/post/232073/
Нажмите здесь для печати.