Posts Tagged: JavaScript

Поведение `for (var i in arr)` в IE8 после расширения Array.prototype

Недавно решил посмотреть, как дела у моего jQuery плагина в IE8. Я думаю, предсказать что было дальше не сложнее, чем предугадать, чем закончится классическая голливудская мелодрама – в скрипте обнаружились ошибки.

Из-за убогости консоли я долго думал в чём дело и столкнулся с необычным явлением. Как известно, IE8, как и все другие десктопные браузеры от Microsoft с момента своего появления является сильно устаревшим браузером. В частности, в нём нет ряда методов из Array.prototype, таких как весьма удобный и относительно часто используемый indexOf(obj). Естественно я знал это и позаботился, добавив к проекту небольшой скрипт missed.js, который кочует у меня из проекта в проект, разрешая проблему недостающего функционала в IE8. Этот скрипт – классическое решение, он содержит ряд объявлений вида:

if (!Array.prototype.indexOf) { 
    Array.prototype.indexOf = function(...) {...} 
}

Не знаю, почему я впервые наткнулся на ошибку только недавно, но в общем это был первый раз, когда я увидел, как после этого будет использоваться код вида for (var i in arr), в случае, когда arr – массив.

Оказывается (с одной стороны конечно же логично, но с другой это далеко не сразу приходит в голову), что в таком случае i принимает значение не только индексов элементов массива, но и забирает не родные методы из своего, т.е. Array прототайпа. Поэтому использование такой конструкции без дополнительных правок (например делать continue, если i не является числом) невозможно.

Обсудив это с коллегой я пришёл к выводу, что лучше для массивов всегда использовать код вида for (var i = 0; i < arr.length; i++).

`for (var i in arr)` behavior in IE8 after extending Array.prototype

Few days ago I decided to see how is my jQuery plugin doing in IE8. I think to predict what happened next is not much more difficult than to guess how a classical Hollywood melodrama ends. There were errors in the script.

Due to the IE console is incredibly poor I spent a lot of time considering what was going and faced an interesting phenomenon. As you know, IE8 like all other desktop browsers from Microsoft corporation is heavily outdated since the time it was released. For instance, there is luck of some methods from Array.prototype, such as very handy and quite popular indexOf(obj). I knew about that of course and that is why I added my missed.js script I use in many projects for a long time, which is a collection of declarations like:

if (!Array.prototype.indexOf) { 
    Array.prototype.indexOf = function(...) {...} 
}

This is strange it was the first time I faced the error only now, but anyhow it was the first time I saw how for (var i in arr) construction will work after extending Array.prototype if
arr is an array.

It turned out (what is clear on the one hand, but is not so predictable on the other) that in this case, i takes not only indices valus, but also not native methods from its (Array) prototype. That is why using such construction is not impossible without additional editings like doint continue if i is not a mumber.

After talking to my college about that I made a conclusion that if you dealing with arrays it is always better to use for (var i = 0; i < arr.length; i++) code.

Блок «Поделиться» в jquery.prettyPhoto

Как-то я узнал, что использовать блоки «Поделиться» типа AddThis и ShareThis в плагинах типа jquery.prettyPhoto или lytebox крайне затруднительно, если хочется, чтобы, например, при нажатии на кнопки «влево-вправо» (пред/след) ссылка обновлялась, а также, чтобы ссылка была верной уже при первом нажатии на элемент, по которому открывается модальное окно, либо при первом посещении страницы, т.е. когда кто-то вставил в адресную строку готовый адрес (в том числе с хэшом). Конечно при использовании share-сервисов с целью делиться каждый раз одним и тем же адресом для всех модальных окон проблем возникнуть не должно, т.е. вопрос вставки в модальное окно самого блока – задача достаточно простая, хотя и тут могут возникнуть некоторые проблемы. Не помню точно, но кажется из-за этого я остановился на сервисе «Поделиться» от Яндекса, который по крайней мере без проблем вставился в jquery.prettyPhoto и каждый раз реагировал на нажатия.

Немного жаль, что история моей работы...

Немного жаль, что история моей работы, т.е. борьбы с необновлением ссылки, пропаданием блока, нуждой кликать на него два раза и т.д. потеряна, но хотелось бы просто поделиться конечным результатом. Во-первых (речь пойдёт о сочетании jquery.prettyPhoto и Yandex.Share) в последний следует добавить аргумент «описание» в метод обновления ссылки.
Далее, надо продумать, в какое место вставлять код, порождающий объект YandexShare. Это будет непосредственно место под контентом в конструкторе prettyPhoto:

social_tools: /* html code or false to disable */
'<div id="photo_shblock" style="text-align: right; margin-right: 21px; margin-top: -27px;">Поделиться:<script>' +
    "var YaShare = new Ya.share({\n" +
    "    onready: function(instance) {\n" +
    "" +
    "    }," +
    "    element: 'photo_shblock',\n" +
    "    elementStyle: {\n" +
    "        'type': 'none',\n" +
    "        'border': false,\n" +
    "        'quickServices': ['vkontakte', '|', 'facebook', '|', 'twitter', '|', 'odnoklassniki', '|', 'lj']\n" +
    "    },\n" +
    "    title: document.title,\n" +
    "    description: 'http://kremer.pro/gallery/',\n" +
    "    image: ''\n" +
    "});\n" +
    "jQuery('#photo_shblock').prepend('<span id=\"share_title\" style=\"color: #DDD; opacity: 0.8\">Поделиться: </span>');" +
'</script></div>'

Теперь в метод changePage(direction) добавляем вызов самописного метода updateYaShare(); после строчки “rel_index = set_position;“.

А в самом конце плагина (перед ‘})(jQuery);‘) добавляем:

function updateYaShare() {
    var hash = location.hash;
    var hashBase = hash.substring(0, hash.indexOf("/") + 1);
    var newHash = hashBase + rel_index + "/";
    var newLink = location.href.substring(0, location.href.indexOf('#')) + newHash;
    window.YaShare.updateShareLink(newLink, document.title, {}, "http://kremer.pro/gallery/");
}

Собственно, вот и всё решение. В работе это можно увидеть в галерее.

Доработка блока «Поделиться» от Яндекса

Иногда хочется иметь возможность использовать сервисы в духе AddThis и ShareThis не на отдельных страничках, например http://domain.name/a и http://domain.name/b, а на одной странице, которая имеет множество различных разделов, имеющих разные адреса, отличающиеся друг от друга хэшом, например, http://domain.name/a#01 и http://domain.name/a#02.

В этом месте возникает проблема – при изменении хэша необходимо обновлять текст ссылки, который хранится в объекте share-сервиса.

Блок «Поделиться» от Яндекса имеет...

Блок «Поделиться» от Яндекса имеет специальный метод для этого: updateShareLink(link_url, page_title, serviceSpecific). Однако у метода отсутствует аргумент «description» – текст, который появляется в сообщении, в жж-ном посте и т.д.

Проблема заключается в том, что при вызове метода этот атрибут слетает, поэтому нам придётся добавить в updateShareLink() ещё один аргумент: desc, даже если мы не хотим обновлять описание ссылки каждый раз при его вызове метода.

Так что добавим в updateShareLink() ещё один аргумент: desc. Для этого найдём соответствующий фрагмент кода плагина (взято из предыдущей версии, но различия всё равно минимальны):

updateShareLink: function (e, d, b) {
    if (!this._loaded) {
        return this;
    }
    var h, i, a, g, c = "",
        j = "";
    if (arguments.length == 1 && typeof arguments[0] == "object") {
        var f = arguments[0];
        e = f.link || af.location.toString();
        d = f.title || aX.title;
        c = f.description || "";
        j = f.image || "";
        b = f.serviceSpecific || {}
    } else {
        e = e || af.location.toString();
        d = d || aX.title;
        b = b || {}
    }
    a = ae(this._block, "b-share__link");
    for (h = 0, i = a.length; h < i; h++) {
        g = a[h].getAttribute("data-service");
        a[h].href = aV(g, ax(g, "link", e, b), ax(g, "title", d, b),
                ax(g, "description", c, b), ax(g, "image", j, b))
    }
    if (this._popup) {
        a = ae(this._popup, "b-share-popup__item");
        for (h = 0, i = a.length; h < i; h++) {
            g = a[h].getAttribute("data-service");
            if (g) {
                a[h].href = aV(g, ax(g, "link", e, b), ax(g, "title", d, b),
                        ax(g, "description", c, b), ax(g, "image", j, b))
            }
        }
        a = ae(this._popup, "b-share-popup__input__input");
        for (var h = a.length - 1; h >= 0; h--) {
            if (a[h] && a[h].tagName.toLowerCase() !== "textarea") {
                a[h].value = e
            }
        }
    }
    return this;
}

Внесём следующие изменения:

updateShareLink: function (e, d, b, desc_) {
    if (!this._loaded) {
        return this;
    }
    var h, i, a, g, desc = "", // g - это имя сервиса
        j = "";
    if (arguments.length == 1 && typeof arguments[0] == "object") {
        var f = arguments[0];
        e = f.link || af.location.toString();
        d = f.title || aX.title;
        // строчка: c = f.description || ""; здесь не нужна
        j = f.image || "";
        b = f.serviceSpecific || {}
    } else {
        e = e || af.location.toString();
        d = d || aX.title;
        desc = desc_ == null ? "" : desc_; // а такую добавляем сюда
        b = b || {}
    }
    a = ae(this._block, "b-share__link");
    for (h = 0, i = a.length; h < i; h++) {
        g = a[h].getAttribute("data-service");
        a[h].href = aV(g, ax(g, "link", e, b), ax(g, "title", d, b),
                ax(g, "description", desc, b), ax(g, "image", j, b));
    }
    if (this._popup) {
        a = ae(this._popup, "b-share-popup__item");
        for (h = 0, i = a.length; h < i; h++) {
            g = a[h].getAttribute("data-service");
            a[h].href = aV(g, ax(g, "link", e, b), ax(g, "title", d, b),
                    ax(g, "description", desc, b), ax(g, "image", j, b))
        }
        a = ae(this._popup, "b-share-popup__input__input")[0];
        if (a) {
            a.value = e
        }
    }
    return this;
}

Кроме того стоит заметить, что некоторые значки в блоке «Поделиться» от Яндекса не такие стильные, как у AddThis, ShareThis и ряда других. Это исправляется легко – находим строчку: url(//yandex.st/share/static/b-share-icon.png) в коде скрипта. Меняем значки, если считаем нужным и сохраняем изображение у себя, заменяя в скрипте путь на свой, например url(/common/icons/yashare/b-share-icon_mod.png).

Однако хочется ещё немного приукрасить кнопочки. Лично я добавил в конец скрипта следующее:

var YSStyle = {
    fadeDuration: 300
};

$("span.b-share-icon").live("mouseover", function() {
    jQuery(this).stop().animate({opacity: "0.8"}, YSStyle.fadeDuration);
    jQuery("#share_title").stop().animate({opacity: "1"}, YSStyle.fadeDuration);
}).live("mouseout", function() {
    jQuery(this).stop().animate({opacity: "1"}, YSStyle.fadeDuration);
    jQuery("#share_title").stop().animate({opacity: "0.8"}, YSStyle.fadeDuration);
});

Теперь изначально иконки яркие, а при наведении плавно бледнеют. Как это выглядит можно увидеть на странице с какой-нибудь фоткой, например этой.