Заметки за 2010 год (страница 4)

Установка предпочитаемого языка для программы

Замечательно, что программы для Mac OS X с минимальными затратами для разработчика можно перевести на любой язык мира. Только не всегда интерфейс с фразами на различных языках выглядит одинаково гармонично. Часто слова приходится сокращать, если они не помещаются в отведенные им места.

Все фразы интерфейса на различных языках находятся в файлах .lproj внутри пакета с приложением. Если удалить, например, ru.lproj, то программа лишится перевода на русский язык. Это так же неплохой способ сэкономить место на диске, которое занимает приложение. Вот только при очередном обновлении, а это особенно касается приложений от Apple, пакет с программой полностью заменяется и все файлы .lproj опять появляются.

Чтобы раз и навсегда выбрать предпочитаемый язык для какого-то конкретного приложения можно добавить соответствующее свойство через команду defaults . Во всей системе принято использовать свойство AppleLanguages для задания предпочитаемой последовательности языков.


defaults write -g AppleLanguages -array ru en de

Эта команда задает последовательность «Русский», «Английский», «Немецкий» для всей системы. Аналогичный эффект достигается в пульте «Язык и текст» настроек.

А если указать доменное имя программы, то список предпочитаемых языков будет сохранен только для нее.


defaults write com.apple.iTunes AppleLanguages -array en

Так мы указываем, что iTunes должен использовать только английский. Хочу подчеркнуть, что это свойство имеет тип «массив» даже если указывается только один язык.

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

Предпочтения удаляются командой

defaults delete com.apple.iTunes AppleLanguages
Комментарии к заметке: 2

JSONP для инициализации страницы

Блоки с динамическим содержимым уже давно принято обновляться асинхронными запросами на сервер. А вот для наполнения их начальными данными эта техника не слишком хорошо подходит из-за видимой задержки в появлении внешнего оформления блока и самого содержимого блока. Чтобы минимизировать эту задержку данные уже должны находиться на странице во время ее загрузки. Очевидно, что при этом HTML захламляется вкраплениями JavaScript, что не слишком хорошо. Компромиссным вариантом может быть загрузка данных с помощью тега script.

Предположим, что на сервере есть контроллер, который отдает модель данных в виде JSON. Пусть URL, по которому можно получить эти данные, будет http://example.com/data/?page=1, а сами данные


{sites: [
    {id: 1, name: "Yandex.ru", url: "http://yandex.ru/"},
    {id: 2, name: "Google", url: "http://google.com/"},
    {id: 3, name: "NotesKeeper", url: "http://noteskeeper.ru/"}
]}

Этот URL используется в асинхронном запросе, чтобы получить новую страницу блока. Если его подставить в тег script , то он, конечно же, будет загружен и исполнен, но возникнет синтаксическая ошибка, так как объект не был использован в операции присвоения или при вызове функции.

Добавим в параметры к URL еще один http://example.com/data/?page=1&callback=App.render и модифицируем контроллер на сервере так, чтобы этот параметр (при его наличии) подставлялся в выходные данные


App.render( {sites: [
    {id: 1, name: "Yandex.ru", url: "http://yandex.ru/"},
    {id: 2, name: "Google", url: "http://google.com/"},
    {id: 3, name: "NotesKeeper", url: "http://noteskeeper.ru/"}
]} );

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

Контроллер интерфейса может иметь, например, такую структуру


var App = (function () {
    // скрытые переменные приложения
    return {
        // публичные методы приложения
        render: function (model) {
            // действия, которые можно выполнить до полной загрузки документа
            $(function () {
                // изменения в документе, который выполняются только
                // после его полной загрузки
                App.update(model);
            });
        },
        update: function (model) {
            // обновляем содержимое блока
        }
    };
}());

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

Эта техника и называется JSONP (JSON with padding). Основное применение она нашла в загрузке данных сторонних сервисов, например, таких как Flickr, Yahoo, Google и многих других. Из-за того, что асинхронные запросы для других доменов блокируются политикой безопасности браузера, использование этой техники наряду с iframe является, пожалуй, единственным возможным вариантом.

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

Оставте свой комментарий

Получаем местоположение пользователя

Если ваши клиенты проживают в разных городах, и для каждого из них вы хотите предложить какие-то товары и услуги где-нибудь поблизости, то, скорее всего, нужно будет сделать селектор со списком городов или регионов. Но будет проще, если мы сами попытаемся определить местоположение пользователя. В этом нет никакого волшебства.

Сопоставляем IP-адрес пользователя его местоположению

У каждого компьютера в сети есть идентификатор — IP-адрес. Он назначается Интернет-провайдером, к которому подключен пользователь. А так как блоки адресов привязаны к конкретному городу, региону или стране, то этого достаточно, чтобы определить местоположение ваших пользователей.

Такую базу адресов можно хранить у себя на сервере, а можно воспользоваться сторонними решениями, например, Maxmind GeoIP database. Maxmind так же предоставляет JavaScript решение , которое можно использовать на своих сайтах:


<script type="text/javascript" src="http://j.maxmind.com/app/geoip.js"></script>
<script type="text/javascript">
$(function () {
    var lat = geoip_latitude();
    var lon = geoip_longitude();
    var city = geoip_city();
    var out = '<h3>Information from your IP</h3>'+
        '<ul>'+
        '<li>Latitude: ' + lat + '</li>'+
        '<li>Longitude: ' + lon + '</li>'+
        '<li>City: ' + city + '</li>'+
        '<li>Region: ' + geoip_region() + '</li>'+
        '<li>Region Name: ' + geoip_region_name() + '</li>'+
        '<li>Postal Code: ' + geoip_postal_code() + '</li>'+
        '<li>Country Code: ' + geoip_country_code() + '</li>'+
        '<li>Country Name: ' + geoip_country_name() + '</li>'+
        '</ul>';
    $("#geo-info").html(out);
});
</script>

Получаем местоположение пользователя через W3C Geo API

Точность определения местоположения через IP-адрес оставляет желать лучшего. Она, как правило, достоверно позволяет определить город. Говорить об определении улицы или даже дома просто не приходится. Другим недостатком этой техники является то, что пользователь может подключаться через прокси- или VPN-серверы, которые располагаются в совершенно других городах.

Комитет W3C предложил рекомендацию для Geo-location API , позволяющее браузеру запросить местоположение у пользователя. Она уже реализована в Firefox 3.5 и в Safari на iPhone.


// if the browser supports the w3c geo api
if (navigator.geolocation) {
    // get the current position
    navigator.geolocation.getCurrentPosition(
    // if this was successful, get the latitude and longitude
    function (position) {
        var lat = position.coords.latitude;
        var lon = position.coords.longitude;
    },
    // if there was an error
    function (error) {
        alert('ouch');
    });
}

По материалам Smashing Magazine

Оставте свой комментарий

Определение IE6 через CSS селектор

Определение версии браузера через переменную User-Agent не всегда работает правильно из-за того, что это значение легко подменить. Разработчики jQuery вообще решили отказаться от его использования, предложив как альтернативу определение особенностей браузера программно. Различные реализации одних и тех же свойств или отсутствие какой-либо реализации позволяет четко выделить Internet Explorer на фоне остальных. Но вот выделить какую-либо версию уже становится большой проблемой.

Следуя предложенной концепции выявления особенностей браузера, я решил использовать CSS селектор tag[class] для обнаружения IE6. В старших версиях IE, как и в других современных браузерах, этот селектор корректно обрабатывается, а в 6-й версии он игнорируется.


(function ($) {
    var cssCode = 'html.jQueryDetectIE6{background-color:#ffffff;}html.jQueryDetectIE6[class]{background-color:#fefefe;}',
        styleElement, html;
    try {
        html = $("html");
        if (html.length) {
            styleElement = document.createElement("style");
            styleElement.type = "text/css";
            if (styleElement.styleSheet) {
                styleElement.styleSheet.cssText = cssCode;
            } else {
                styleElement.appendChild(document.createTextNode(cssCode));
            }
            $("head").prepend(styleElement);
            html.addClass("jQueryDetectIE6");
            $.support.cssClassSelector = /254|fe/ig.test(html.css("backgroundColor"));
        }
    } catch (e) {
        // nothing here
    } finally {
        // cleanup
        if (html && html.length) {
            html.removeClass("jQueryDetectIE6");
        }
        if (styleElement && styleElement.parentNode) {
            styleElement.parentNode.removeChild(styleElement);
        }
    }
}(jQuery));

Во время своего выполнения функция добавляет необходимый стиль в тег head и проверяет их влияние на элемент. Результат проверки заносится в поле $.support.cssClassSelector.

Флаг хотелось установить еще до полной загрузки документа. По этому требовалось свести к минимуму модификацию DOM-документа. Это удалось решить через проверку цвета у корневого элемента html.

Другая проблема возникла из-за различного поведения браузеров при добавлении стилей через JavaScript. IE и Safari начинают применять эти правила только, если они были добавлены в тег head. Тут нет побочных эффектов, так как этот элемент уже существует на момент выполнения кода. Но сам элемент style нужно сформировать особым образом. В IE есть свойство styleSheet у элемента, которое позволяет работать с правилами. В свою очередь у объекта styleSheet есть поле cssText , которое позволяет получать и устанавливать текстовое представление правил. Хочу отметить, что изменить свойство cssText можно только однажды. Если все же потребуется его поменять, то нужно будет удалить старый элемент style и добавить новый с обновленными значениями.

Оставте свой комментарий

Внедряем карты Google Maps на сайт

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

Для хорошей организации кода используем пространство имен, выделенное специально для нашего приложения.

var APP = {};

Собственно, код модуля:


(function () {

    // TODO!!! Нужно будет получить свой уникальный ключ
    var GOOGLE_MAP_KEY = "abcdefg";

    /**
     * Карта Google
     * @param root  корневой DOMElement, в котором будет рисоваться карта
     * @param model массив маркеров
     */
    APP.Map = function (root, model) {

        var map;

        function drawMap (map, data) {

            var i, bounds = new google.maps.LatLngBounds();

            for (i = 0; i < data.length; i++) {
                bounds.extend(new google.maps.LatLng(data[i].lat, data[i].lng));
            }

            map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));

            // создаем базовый объект маркета
            var baseIcon = new google.maps.Icon(google.maps.DEFAULT_ICON);

            for (i = 0; i < data.length; i++) {
                (function (s) {
                    var icon, marker;
                    icon = new google.maps.Icon(baseIcon);
                    icon.image = s.icon || icon.image;
                    marker = new google.maps.Marker(new google.maps.LatLng(s.lat, s.lng), icon);
                    map.addOverlay(marker);

                    google.maps.Event.addListener(marker, "click", function() {
                        // обрабатываем клики по маркеру
                    });
                })(data[i]);
            }
        };

        // загружаем карту, если это необходимо, и рисуем маркеры
        try {
            if (typeof google === "undefined") {
                $(document).one("mapLoaded", function () {
                    map = new google.maps.Map2(root);
                    map.addControl(new google.maps.SmallZoomControl());
                    $("body").bind("unload", google.maps.Unload);
                    drawMap(map, model);
                });
                $.getScript("http://www.google.com/jsapi?key=" + GOOGLE_MAP_KEY + "&callback=APP.Map.callback");
            } else {
                drawMap(map, model);
            }
        } catch (exc) {};
    };

    APP.Map.callback = function () {
        google.load("maps", "2", {
            "callback": function () {
                $(document).trigger("mapLoaded");
            },
            // опциональный параметр, задающий язык карты
            "language": "ru"
        });
    };
})();

Для нормальной работы модуля нужно получить ключ для доступа к API Google Maps и указать его в соответствующей переменной.

А теперь пример использования модуля.


// инициализируем карту тестовыми данными
$(function () {
    // центр в Челябинске
    var center = {lat: 55.16, lng: 61.4},
        mapHolder = $("#map-object").get(0),
        markers = [], i;

    if (mapHolder) {
        // случайно несколько маркеров около заданного центра
        for (i = 0; i < 10; i++) {
            markers.push({
                lat: center.lat + (Math.random() - 0.5) / 30,
                lng: center.lng + (Math.random() - 0.5) / 15
            });
        }
        markers.push(center);
        APP.Map(mapHolder, markers);
    }
});

Оставте свой комментарий