Pull to refresh

Comments 81

Пишите комменты/отзывы, товарищи. Особенно когда минусуете ;-)
статья хорошая.

воможно минусы ставят за большие простыни исходников. Стоило в начале представить пример, который бы описывал о чем речь пойдет, а потом описать реализацию. И да, полные исходники в теле статьи не нужны и вредны. Если бы вы выделили только значимые куски кода и описали что он делат, то читать статью было бы интереснее и легче. Весь остальной код можно прикрепить ссылкой, кому надо пусть разбирается с оригиналами.

* кроме того, очень плохо, когда в статье отсутствуют ожидаемые изображения
Спасибо за отзыв, сейчас вытащу пример наверх. А рисуки — надо перезалить, но я не знаю — куда лучше :(
Если рисунок не отобразился — прямая ссылка.


404 =\ Сами пробовали открыть?
Эх, вечно проблемы с этими рисунками. Спасибо, перезалью их куда-нибудь на другой сервер. Кстати, не подскажете достойный?
Я пикассу использовал. Нет проблем с большой нагрузкой.
var pattern = "^(([^:/\\?#]+):)?(//(([^:/\\?#]*)(?::([^/\\?#]*))?))?([^\\?#]*)(\\?([^#]*))?(#(.*))?$";
var rx = new RegExp(pattern);
var parts = rx.exec(url);


Сокращаем до одной строки:
var parts = /^(([^:/\\?#]+):)?(//(([^:/\\?#]*)(?::([^/\\?#]*))?))?([^\\?#]*)(\\?([^#]*))?(#(.*))?$/.exec(url)

Создание гетеров, сетеров… Вы вообще о чем? есть адекватное решение навроде:

this.holder = [];
var val = function(key, val) {
if (!val) return this.holder[key];
this.holder[key] = val;
}

Джаваскрипты они вообще не о том, чтобы такие интерфейсы у классов писать. Если постараться — ваш код сожмется раза в 2 если не больше.
Сократить RegExp до одной строки — да, можно. Но это не суть.

А вот геттеры и сеттеры — суть. Тут вы либо не поняли мою идею, либо не дочитали статью :) Соль — в псевдо-аттрибутах. И решения для IE я пока в глаза не видел.
Видимо действительно не уловил сути. Пример жизненный приведите пожалуйста. Ато так много кода, а вокруг чего вся кутерьма, да еще и с магическими методами — не до конца ясно.

Насчет универсального сеттера комментарием выше — извините, быстро по коду пробежал, не увидел у вас метод protocol.
Да ничего, понимаю, что много букв, но не люблю, когда часть — спрятана от глаз. Примеры — вынес из ката наверх. Ну и могу прямо здесь привести:
// Пусть текущий URL = 'http://my.site.com/somepath/'
var u = new URL('relative/path/index.html')
u.href // my.site.com/somepath/relative/path/index.html
u.href = '/absolute/path.php?a=8#some-hash'
u.href // my.site.com/absolute/path.php?a=8#some-hash
u.hash // #some-hash
u.protocol = 'https:'
u.href // https://my.site.com/absolute/path.php?a=8#some-hash
u.host = 'another.site.com:8080'
u.href // https://another.site.com:8080/absolute/path.php?a=8#some-hash
u.port // 8080
// и так далее, и тому подобное

* This source code was highlighted with Source Code Highlighter.
Работает в FF3+ (может и в 2+, не пробовал) и в IE6+. Можно ещё докрутить для других.
Нет, я не о том. Буквы я худо-бедно прочитал.

Я о жизненном применении этой библиотечки. Вам часто приходится джаваскриптом модифицировать и изменять ссылки? Как эти решения могут помочь сторонним разработчикам?
Скажу честно, данная тулза возникла именно из практических нужд.
И я видел уже несколько кустарных разработок подобного назначения в больших JS-проектах, таких, как TinyMCE. В RTE часто имеешь дело со ссылками на ресурсы. И эти ссылки нужно обрабатывать в real-time.

Конкретно мне надо было распарсить текущий URL и изменить/добавить новый параметр в search, с последующим редиректом.

Можно придумать ещё.
Спасибо за разъяснение. Вы бы об этом упомянули вначале статьи.
" — Как ни крутись, без RegExp не обойтись"
Претендует на народную мудрость:-)
А чем вам не угодили обычные a.hash('asd') a.hash()?
Зачем эти танцы с бубнами?
Собственно поэтому версия 2.0 альфа :) Просто решил поделиться решением для псевдо-аттрибутов для IE. В продакшене я бы использовал именно a.hash('asd') и a.hash().
наивно полагать, что никто не додумался до этого раньше ;-)
способ довольно грязный, ибо в узлах и так много всякого мусора
к тому же полиморфные акцессоры позволяют использовать цепочки:
var uri= $.url( '/xxx' ).hash( 666 ).post( 8080 ).href()

ещё, хранение данных в замыканиях — сильно усложняет отладку.
лучше тогда уж использовать проперти типа таких: plugins.jquery.com/files/jquery-property.js_1.txt
Цепочки — легко реализовать. ранение данных в замыканиях — хорошо тем, что с удалением объекта эти данные соберёт garbage collector, в отличии от использования сторонних хранилищ этих данных.

Да, метод грязный (для IE), но другого я не нашёл :(
> Цепочки — легко реализовать.

пример?

> ранение данных в замыканиях — хорошо тем, что с удалением объекта эти данные соберёт garbage collector, в отличии от использования сторонних хранилищ этих данных.

глупость какая-то

1) Цепочки: в моей реализации заменить
setPort: function(val) {
//…
return data.port;
}
на setPort: function(val) {
//…
return this;
}
И вызывать этот метод в нужном контексте (setPort.call(necessaryScope, val))

2) Не вижу ничего глупого. В предложенном вами решени plugins.jquery.com/files/jquery-property.js_1.txt — по сути хранит данные вне целевого объекта, и когда этот объект исчезнет, данные его останутся в этом «внешнем хранилище» навсегда.
1. ага, ещё зафигачить эти методы в инстанс и получить в нём оба интерфейса…

2. почему внешнее хранилище не будет уничтожено? о_0''
search хорошо бы еще разбирать на пары key: value и соответственно менять их по отдельности по необходимости.
А так библиотека полезная получилась бы, например при изменении каких-то фильтров, менять ссылки на связанные страницы где эти же фильтры используются.
function URL( href ){
var url= document.createElement( 'a' )
url.href= href || document.location.href
return url
}

var link= new URL( '/xxx' )
link.hash= 666
alert( link )
Да, интересно, не подумал :)
Но мой способ (v.1 final) — даёт отвязку от DOM'а. И применим также в server-side JS. Ну и для Actionscript.
гомэн, промахнулся ^_^''
Мне почему то в голову приходит другое простое решение:

var u = document.createElement('A');
u.href = 'relative/path/index.html'
u.href // my.site.com/somepath/relative/path/index.html
u.href = '/absolute/path.php?a=8#some-hash'
u.href // my.site.com/absolute/path.php?a=8#some-hash
u.hash // #some-hash
u.protocol = 'https:'
u.href // https://my.site.com/absolute/path.php?a=8#some-hash
u.host = 'another.site.com:8080'
u.href // https://another.site.com:8080/absolute/path.php?a=8#some-hash
u.port // 8080
// и так далее, и тому подобное


* This source code was highlighted with Source Code Highlighter.

Может я конечно чего-то не понимаю, и ваш велосипед на самом деле чем то лучше :)
Да, признаю — это неплохой вариант. Но моя реализация лучше в двух моментах:
1) Отвязанность от DOM-модели. Возможность использовать в server-side JS, или в Actionscript
2) Наглядность. Видно, как этот велосипед устроен внутри.
а смысл создавать велосипед, который копирует нативный фукнционал?
server-side JS, конечно, да, не поспоришь, но вещь в наших краях редкая.
может, подумать в сторону расширяемости какой-нибудь, ну там сравнение ссылок, еще чего-нибудь придумать?
u.href // my.site.com/somepath/relative/path/index.html
u.catalogues // [«somepath», «relative», «path»]

еще можно mailto проработать, к нему можно в get-параметрах тему и текст сообщения передавать

и есть псевдопротоколы, вроде magnit или javascript, для них наверняка можно что-нибудь полезное придумать
Спасибо, есть куда стремиться :)
Да, сравнение реализовать не очень сложно. Да и ещё чего-нибудь можно придумать. То, чего ещё нет в нативном функционале, но что нужно.
Вы уж меня извините, но:
1. Думаю, что в «в server-side JS, или в Actionscript» наверняка есть что-то встроенное. Отвязанность от DOM? Это в реализации для IE, где вы создаете элемент DIV? Вариант для Firefox будет работать только для браузеров на базе gecko, так что о server-side и actionscript говорить не приходится.
2. Наглядность только в том случае, если Вы хотите показать как это написать самому. Включать в свое приложение дополнительный код, который работает только в двух браузерах, причем по разному — когда можно обойтись более простым решением — несколько нерационально.

Ну и по коду, не очень хорошая реализация. Я бы остановился на второй реализации, только бы выправил с точки зрения алгоритмической части. Позвольте несколько советов.
  this.search = function(val) {
        if (typeof val != "undefined") {
            search = val;
        }
        return search;
    }

Если вы хотите проверить передан параментр или нет, то лучше это делать следующим образом:
  this.search = function(val) {
        if (arguments.length) {
            search = val;
        }
        return search;
    }

Следующий таинственный фрагмент:
this.href(this.protocol() + '//' + this.host() + this.pathname() + this.search() + this.hash());

Почему не
this.href = protocol + '//' + host + pathname + search + hash;

Ведь методы сделают тоже самое + у вас не будет проблем с рекурсией.
Стремимся к простосте — вместо:
if (val.indexOf("/") != 0) { // relative url
   var _p = (pathname || window.location.pathname).split("/");
   _p[_p.length - 1] = val;
   val = _p.join("/");
}

например так
if (val.charAt(0) != '/') { // relative url
  val = pathname.replace(/(\/|)$/, '/' + val)
}

Про window.location.pathname лучше забыть. Ибо
// находимся на http://habrahabr.ru/blogs/javascript/65407/
var u = new URL('http://domain.ru');
u.pathname('foo/bar/index.html');
u.href // ожидаете http://domain.ru/foo/bar/index.html получите http://domain.ru/blogs/javascript/65407/foo/bar/index.html


Ну и далее далее…
1) Насчёт if (arguments.length) { — согласен, так лучше.
2) «Следующий таинственный фрагмент:» — вы что-то путаете. Первый фрагмент — для одной реализации. Второй фрагмент — для второй реализации.
3) «val.indexOf(»/") != 0" == «val.charAt(0) != '/'» — одно и то же.
4) Использовать «pathname.replace(/(\/|)$/, '/' + val)» более расточительно, чем мой вариант со split + join, хоть он и более громоздкий. Ну и мой вариант более читабелен.
5) «Про window.location.pathname лучше забыть» — вы наверное имеете ввиду relative pathname? Т.к. в целом про pathname ни в коем случае забывать нельзя :) Но если моя реализация действительно работает так, как вы описали, то это конечно бага. Зафикшу.
Извиняюсь, насчёт №2 — тут я перепутал. Вы предлагаете использовать this.href = protocol + '//' + host + pathname + search + hash; но тут 2 момента:
1) Внутри ф-ции updateURL (из которой вы и привели фрагмент кода) нет никаких сведений ни про какие host, pathname, search и hash, т.к. они находятся внутри замыкания конструктора.
2) this.href = /* всё, что вы написали */ — привдёт к переопределению метода this.href.

А чтоб оно работало, надо реализовывать всё совсем по-другому :-)
Да, this я забыл убрать.
Насчет замыкания — только сейчас заметил что вынесена функция. Что не совсем логично, так как она используется (вызывается) только внутри класса. Зачем она вынесена? Только потому что используется всеми экземплярами? Может стоит поместить внутрь конструктора? Вне конструктора она не нужна — и тогда сможете вызывать ее updateUrl() вместо updateUrl.call(this)
Да, я вынес updateUrl из объекта и из конструктора потому, что её требуется всего 1 экземпляр, и он должен быть спрятан от глаз в замыкании.
Несколько странный подход… от кого прячем? :)
Ну вот представьте — у вас объект URL. updateUrl(...) мог бы находится в this, тогда у наблюдательного пользователя (скажем, использующего console.dir) возникнет вопрос — что оно тут делает? С чем его есть? Ответ — не с чем, это служебный метод. Поэтому его надо прятать с глаз долой.
Как это сделать? Можно спрятать в конструктор. Тогда будет создаваться по новенькой ф-ции updateUrl с каждым новым объектом. Но это неоправдано, нужна только одна ф-ция updateUrl.
Значит надо вынести и из конструктора, и из прототипа. Значит это должна быть внешняя ф-ция, но спрятанная в одноразовом замыкании (function(){/**/})()

Вот так я размышлял, когда писал код.
2. второй фрагмент переписаная строка из вашей функции updateURL().
4. чем же более расточительно? поиск и замена вместо split (то же поиск, но плюс разбиение на массив) + join. В вашем оформление он не очень то и читабельней.
5. да я имел ввиду что в случае работы с некоторым URL не стоит приплетать значения из window.location в случае если что-то опущено в адресе с котором работает объект, иначе можно получить неожиданные сюрпризы.

ЗЫ Все вышеизложеное было мое ИМХО. Не хотелось бы заниматься буквоедством и холиварами :)
Нашёл большой минус подхода «var u = document.createElement('A');» — неадекватное поведение в IE6-8. Так не пойдёт.
var u = document.createElement('A');
u.href = 'relative/path/index.html';
alert(u.href); // relative/path/index.html, а не my.site.com/somepath/relative/path/index.html

Одинаково неправильно в IE6, IE7, IE8
Вы издеваетесь, или троллите меня? :)
Вы предложили нерабочий (но зато native) вариант взамен моего рабочего, вот что. Потому я и сказал, что так не пойдёт.
в каком месте он не рабочий? зачем тебе пренепременно абсолютизированные ссылки?
Ссылки, к примеру, нужны именно абсолютные, когда я в РТЕ редактирую статью и вставляю ссылку на ресурс с этого же хоста (бывают такие нужды).

Да и разнобой принципов работы в разных браузерах (в FF — absolute, в IE — relative) — неприемлемо.
это повод пропатчить PTE, чтобы он научился абсолютизировать относительные урлы, если он ещё не умеет этого делать.

неприемлемо почему? о_0
для задачи «распарсить текущий URL и изменить/добавить новый параметр в search, с последующим редиректом» и для большинства других — вполне годится.
Спасибо! Хорошо, доступно, полезно!
Полезно для небольших вещей, но, ИМХО, не окупает килобайтов кода.

a = new URL('http://user:pass@host/') делает бяку (хотя такие вещи попадаются в жизни реже, чем необходимость продублировать window.location)

В AS2/AS3 все равно без отвертки не войдет (хотя бы потому что используете сокращенную форму регулярок).

Для AS3 нашел такую вещь:
manfred.dschini.org/2008/05/12/as3-url-class/
Надо будет почитать, подкрутить :)
Забыли ненавязчиво о пароле и юзере. Смотрим RFC 1738 Uniform Resource Locators (URL) — www.ietf.org/rfc/rfc1738.txt
Для IE можно использовать элемент COMMENT (msdn) чтобы не засорять левыми дивами.
Спасибо, содержательно. Покопаюсь, как будет время :)
Хорошая статья. Но есть замечание к вопросу разбора URL. Все Вами написанное можно было бы записать чуть-чуть короче (за исключением регулярного выражения), только это не покрывает проблем, развитых в статье:

String.prototype.parseUrl = function()
{
	var matches = this.match(arguments.callee.re);

	if ( ! matches ) {
		return null;
	}

	var result = {
		'scheme': matches[1] || '',
		'subscheme': matches[2] || '',
		'user': matches[3] || '',
		'pass': matches[4] || '',
		'host': matches[5],
		'port': matches[6] || '',
		'path': matches[7] || '',
		'query': matches[8] || '',
		'fragment': matches[9] || ''};

	return result;
};

String.prototype.parseUrl.re = /^(?:([a-z]+):(?:([a-z]*):)?\/\/)?(?:([^:@]*)(?::([^:@]*))?@)?((?:[a-z0-9_-]+\.)+[a-z]{2,}|localhost|(?:(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.){3}(?:(?:[01]?\d\d?|2[0-4]\d|25[0-5])))(?::(\d+))?(?:([^:\?\#]+))?(?:\?([^\#]+))?(?:\#([^\s]+))?$/i;
Конечно же у этого решения тоже можно найти недостатки.
Если ставить задачу, как разовый парсинг URL, то да, ваш вариант отлично подходит (за исключением засорения String.prototype). И я вижу вы потрудились над RegExp'ом ( устрашающе выглядит с первого взгляда :-) )
Здесь два варианта — засоряем глобальную или локальную область :). Регекс написан был давно, то ли на PHP, то ли на Perl и лишь перенесен в Javascript. Страшый же он от того что, пытается покрыть максимально возможные варианты (но не все) — protocol://user:pass@host:port/path?query#hash (fragment — небольшое разночтение с Вашим hash). Под host подразумеваются доменные имена, IP-адреса и localhost.
Автор, вы перестарались, первоначальная задача была «распарсить текущий URL и изменить/добавить новый параметр в search, с последующим редиректом.», для этого достаточно 1 регулярного выражения. А если там что-то более сложное, лучше делать это на стороне сервера, чем писать простыню тормозящего загрузку страницы яваскрипта.

И забейте вы на эту имитацию protected methods через .call(this), пишите нормально.

* Вот как же я не люблю, когда видимо пришедшие с других языков типа Явы в веб-программирование, пытаются и тут делать монстры-фреймворки на все случаи жизни.
Это никакой не монстр-фреймворк, и ниоткуда он сюда не перенесён. К тому же я уверен в его востребованности, во всяком случае для меня.
А можно пример «нормального» кода? Объясните, пожалуйста, почему подобная реализация столько плоха? (или ссылки на объяснения приведите)
Реализация плоха тем, что содержит много кода, что отрицательно сказывается на скорости загрузки и производительности сайта на клиентской стороне.

Как я понял, из смутного объяснения автора, ему надо было получать полный УРЛ ресурса из относительного (видимо прибавить http:// и адрес сайта в начало если их там не было), делается это примерно так:

url = (url.substring(0, 1) === '/')? (url = 'http://' + host + url): url; // добавляем адрес сайта к отнсительному УРЛ (начинается со слеша)

if (!/^(\w+):\/\//.test(url)) { url = 'http://' + url); } // Если УРЛ не начинается с протокола, приписываем http://

Если надо, наоборот, парсить УРЛ, то тут придется поизвращаться с регекспами, типа того: var matches = /^(\w+:\/\/)?([a-z0-9_.])((/[^?])(.*))?$/i.exec(url)  - выделяет протокол, адрес узла, путь и строку параметров (простейший пример).
Вообще я спрашивал относительно 2 и 3-го абзацев вашего коммента:
И забейте вы на эту имитацию protected methods через .call(this), пишите нормально.

* Вот как же я не люблю, когда видимо пришедшие с других языков типа Явы в веб-программирование, пытаются и тут делать монстры-фреймворки на все случаи жизни.
Можно например начинать имена с подчеркивания, и соответсвенно не обращаться к методам, начинающимся с подчеркивания, снаружи. Правда, из-за этого половину кода занимают подчеркивания, но лучше же чем писать .apply(this)? Если в языке нет этой опции — по моему не стоит ее пытаться имитировать.

А то мне это напоминает вещи вроде «javascript на php» — попытка сделать библиотеку, чтобы писать javascript используя php-функции, изврат же.
Ничего принципиально нового не увидел. Все уже давно существует, например тут mootools.net/docs/more/Native/URI
А как же getter'ы для IE? Неужели не зацепило? :-)
Как я понял там setter-ы а не getter-ы. В результате получаем уебанский костыль — div, который должен работать как объект класса URL.
Ради этого не стоит заморачиваться. Лучше забить на ie6, ie7 или использовать .get() и .set(). Фрэймворки упрощают не только написание логики приложения но и вспомогательных классов(плагинов).
про onpropertychange не знал, но врятли когданить буду использовать
Да, это костыль. И я бы и сам не стал его использовать в продакшне. Но это псевдо-атрибуты, и теоретически штука интересная.
В IE8, кстати, нормальных геттеров/сеттеров так и не сделали — только для DOM-объектов (опять же), но уже не через onproperychange.
Насчёт варианта с Mootools — во-первых, мне не нужен Mootools для этой штуки, а я так понимаю, что мне придётся его включать в проект. Во-вторых, Mootools загрязняет нативные объекты (в отличии от jQuery), что ИМХО тоже не хорошо.
Стоит еще учесть, что хвост адреса может быть немного не стандартный, например в SWFAdress библиотеке не раз видел вариант типа

...html&a=1&b=2#/f/o/l/d/e/r/s?c=3&d=4

и браузеры это съедают с легкостью
var u=new URL('test.html&a=1&b=2#/f/o/l/d/e/r/s?c=3&d=4')
u.hash // #/f/o/l/d/e/r/s?c=3&d=4

Тут всё ок, всё, что правее первого # == hash
Ну вот я и говорю, что не плохо бы и его парсить, тогда уже организовать это в библиотечку и раздавать/продавать всем желающим, тот же SWFAdress парсит весь хэш, но весьма неказистым образом.
Хорошая идея, спасибо. Надо подумать над этим. Наверное, таки докручу.
Автор, Вы неподражаемы!
Прежде всего пара замечаний:
Ошибка в Вашем же схематическом изображении — HOST является контейнером для hostname и port — так зачем же Вы их стрелочками-то аттачите в HREF?
При изменении любой из частей URL должны обновляться другие. — так и не постиг, как именно «другие» должны обновляться??? ИМХО связь (да и то — лишь теоретическая, бо никто не мешает Вам использовать порты и протоколы по своему усмотрению) в URL есть лишь в парах http => 80 и https => 443 — Вы об этом?
И один вопрос —
Конкретно мне надо было распарсить текущий URL и изменить/добавить новый параметр в search, с последующим редиректом.
Это конкретно все, что нужно было сделать?

var url_replace = function (new_text,del_old) {
var _stuff,_result,_test_url = window.location.href;
_stuff=((_stuff=test_url.match(/\?([^#]+)/))&&_stuff[1]); // при отсутствии search полУчите null
// теперь работайте с Вашим search
if (del_old&&_stuff) _result=test_url.replace(/\?([^#]+)/,'?'+new_text); //если нужно перезаписать search
else _result=test_url.replace(/(#)|$/,(!_stuff?'?':'&')+new_text+'$1'); // если нужно дополнить search
return _result;
}


* This source code was highlighted with Source Code Highlighter.

Вызывайте url_replace('foo=bar') для дополнения или url_replace('foo=bar',1) для замены строки search
Нет, я не о портах 80 и 443. Я про обновление href при, скажем, изменении port, и про обновление href & hostname & port при изменении host. И т.д. Изменяется один аттрибут => должны обновиться ещё парочку.

И я понимаю, что есть решения проще и меньше по объёму кода, но они не универсальны и не поддерживают вышеупомянутое обновление частей URL
Агрегируйтесь! :)
Меняя host — его и меняйте, какая вам разница до его составляющих?
PS. ИМХО решение задач в общем виде — убийство времени.
PPS. Меня там тоже немного покоцали, если решите променять универсальность на скорость и объем — ВЕЛКАМ!
Sign up to leave a comment.

Articles