2 февраля 2014 г.

Скользкие моменты при работе c data атрибутами в Jquery

Когда вы задаете или получаете какое-нибудь значение хранящееся в data-атрибуте DOM элемента JQuery  делает часть грязной рутинной работы за вас.
Что бы было понятно о чем речь, пример Data атрибута data-text-value
<div data-text-value="abc" id="example">...</div>
Вы можете получить значение или задать:
$("#example").data("text-value")
$("#example").data("text-value","123")

Метод $.data является оберткой над объектом dataset DOM элемента. При этом когда вы пытаетесь считать хранимое значение (строку), Jquery конвертирует значение в ближайший по смыслу тип:
"true" => true,
"123" => 123
и т.п. При этом еще кэширет значение чтобы не парится с распарсиванием в будущем.

Вот здесь и можно подскользнутся на банановой кожуре. Если через дата data атрибут задать такое число выходящее за 32 бита 83735748785296303. То можно получить что нибудь отличное, к примеру 83735748785296300. По всей видимости для приведения строки в число используется функция parseInt:
parseInt("83735748785296303")
#: 83735748785296300

Код для доказательства:
var d = document.createElement('div')
d.innerHTML = '<div data-value="83735748785296303"></div>';
$(d).data('value') == 83735748785296300

13 октября 2013 г.

исполняемые комментарии в Internet Explorer

Обнаружил для себя новую приблуду в IE. Когда делал комит в SVN получил предупреждение, что с поставленным в js коде комментарием что-то не так. Нагуглил инфы и обнаружил,что  Interner Explorer может исполнять js код оставленный в комментариях. Код должен:
  1. обернут  символами собачками `@` ;
  2. между собачками и символами открывающими/закрывающими комментарии не должны быть пробелы.
Такой код выполнится в IE8, IE9, но в других будет проигнорирован:
/*@ document.body.innerHTML="123" @*/

Ивроде и не баг. Но почему то я раньше с ним негде не встречался. Осталось, теперь придумать, как это можно использовать.

Подробнее можно почитать
http://support.microsoft.com/kb/323487/en
http://stackoverflow.com/questions/3894079/multiple-js-comment-bug-in-ie

29 сентября 2013 г.

Тестирование почтовой рассылки с помощью blat

Для автоматизации процесса верстки - тестирования почтовых рассылок под Windows я использую консольный почтовый сервер blat.
Загрузить blat можно с сайта проекта http://www.blat.net.

После распаковки в архиве. Перед тем как начать использовать утилиту, необходимо настроить параметры учетной записи smtp сервера.
Все указанные параметры будут сохранены в реестре.
#: blat.exe -install -server smtp.server.ru -port 25 -f your@mail.box -u your@mail.box -pw __password__
где smtp.server.ru адрес smtp сервера твоего любимого почтового сервиса. smtp.server.ru - учетная запись и  имя отправителя. __password__ пароль.

Как послать письмо с вложением
#: blat.exe -charset windows-1251 -to mail@yandex.ru -subject "Тема" -body "Тело письма" -attach "attach.txt"
Письмо по умолчанию будет отправлено в utf-8 (о чем следует помнить).  Ключ -charset windows-1251 у меня почему то не срабатывает.

Как отправить html файл (сверстанное письмо)
#: blat.exe body.html -s "Test mail" -i "Cybergavin Tester" -to mail@yandex.ru,mail@mail.ru,mail@gmail.com -html
Описание ключей:
-s => Тема письма ( тема письма по умолчанию "Contents of file: stdin.txt" )
-i => Имя отправителя (адрес отправителя указывается при настройке параметров smtp сервера)
-to => Адреса назначения через запятую
body.html => файл с версткой письма
-html => ключ для указания mime типа документа - HTML (Content-Type : text/html)

Если письмо содержит графику с сервера. То на время тестирования ее можно брать из расшаренной папки google drive. Google drive позволяет делать статические сайты из расшаренных папок (как и DropBox), поэтому в них можно сложить необходимую графику.

Полезные gem-ы ruby. chunky_png

Ruby удобный язык для создания подсобного инструментария. И основную ценность придает ему доступная система пакетов gem-ов. Одним из таких пакетов является gem chunky_png. [ <) https://github.com/wvanbergen/chunky_png ] chunky_png добавляет легкий api для работы с png. И в основном я его использую для генерации спрайтов - склейки png с небольшими трансформациями. Собственно, для этих целей он используется в compass.

Ставим gem
#: gem install chunky_png

Пример склейки png #1
require 'chunky_png'

files = ['btn1.png','btn2.png','btn3.png','btn4.png','close.png']
# Создадим png файл в который все поместим все картинки. 1-й параметр width, 2-й - height
png = ChunkyPNG::Image.new(70,70*4+32, ChunkyPNG::Color::TRANSPARENT)
yPos=0
files.each{|fname| # Перебираем файлы
    img = ChunkyPNG::Image.from_file(fname)
    png.compose!(img,0,yPos) # размещаем картинку в нужном месте
    yPos=yPos+img.dimension.height
}
png.save("btnSprites.png")

Пример склейки посложнее #2
require 'chunky_png'

# в спрайт нужно вставить оригинальное изображение, но иногда и отцентрировать в заданном периметре
files = [{
            :file => "attention.png" 
        },{
            :file => "check.png"
        },{
            :file => "coin.png"
        },{
            :file => "desctop.png",
            :width => 50, # зададим ширину и высоту квадрата в центр которого нужно вставить изображение
            :height => 50
        },{
            :file => "mobile.png",
            :width => 50,
            :height => 50
        },{
            :file => "question.png"
        },{
            :file => "letter.png",
            :width => 50,
            :height => 50
        }]

file_buf = []
max_width = 0
max_height = 0
css = "" # попутно сгенерируем css, раз мы и так знаем все координаты
css_x_pos = 0
css_y_pos = 0
save_file_name = "sprite" # название спрайта
css_class_name_prefix = ".home-room-free-fm-" # а в css будем вставлять классы с таким префиксом

files.each{|image|
    # вставляем оригинальное изображение
    if image[:width].nil? || image[:height].nil? 
        img = ChunkyPNG::Image.from_file(image[:file])
    # или  помещаем изображение в заданный периметр
    else
        img = ChunkyPNG::Image.new(image[:width],image[:height], ChunkyPNG::Color::TRANSPARENT)
        buf = ChunkyPNG::Image.from_file(image[:file])
        x_pos = ((image[:width] - buf.dimension.width)/2).round
        y_pos = ((image[:height] - buf.dimension.height)/2).round
        #puts "image[:width] `#{image[:width]}`, img.dimension.width `#{img.dimension.width}`, x_pos: `#{x_pos}`"
        img.compose!(buf,x_pos,y_pos)
    end
    
    if img.dimension.width > max_width
        max_width = img.dimension.width
    end
    max_height = max_height + img.dimension.height
    file_buf << img
}
png = ChunkyPNG::Image.new(max_width,max_height, ChunkyPNG::Color::TRANSPARENT)

# помещаем получившиеся фрагменты в спрайт
file_buf.each_with_index{|file,index|
    png.compose!(file,css_x_pos,css_y_pos)
    # а данные о координатах сохраняем в CSS:
    css = css +"#{css_class_name_prefix}#{files[index][:file]}{\n\tbackground:url(#{save_file_name}.png) #{css_x_pos > 0 ? (-css_x_pos).to_s+"px":"0"} #{css_y_pos > 0 ? (-css_y_pos).to_s+"px":"0"} no-repeat;\n\twidth:#{file.dimension.width}px;\n\theight:#{file.dimension.height}px;\n}\n"
    css_y_pos = css_y_pos+file.dimension.height
}
#css можно сохранить в отдельный файл

Рисование окружностей:
require 'chunky_png'

png = ChunkyPNG::Image.new(57, 57, ChunkyPNG::Color::TRANSPARENT)

png.circle(28, 28, 27, ChunkyPNG::Color(0x7b0f34FF), ChunkyPNG::Color::TRANSPARENT)
png.circle(28, 28, 25, ChunkyPNG::Color::TRANSPARENT,ChunkyPNG::Color(0x7b0f344D))
png.save("out3.png",:fast_rgba)
Гем поддерживает возможность рисования. Но по пиксельно, поэтому получившиеся окружности смотрятся хуже в отличие от прямых линий, чем если это делать в photoshop или gimp.

В место заключения. С помощью chunky_png можно с автоматизировать рутинные действия с графикой. Не дергать дизайнера при каждом изменении в наборе фрагментов.

10 июля 2013 г.

Вариант дропдаун меню

В своем уголке самописных костылей я хочу представить концепт контекстное меню (дропдаун меню) реализованного на inlin-овых js скриптах с CSS3 переходами. Бэкендеры на работе обратились ко мне с просьбой накастылять нехитрую реализацию контекстного меню, содержимое которого они могли править не сильно погружаюсь в js код. Грубо говоря копипастить на страницу чистым php. Полученную демку я представляю здесь.

Достоинства и недостатки:
+ не требует предварительной инициализации объектов, не требуется ожидания готовности DOM;
+ удобный доступ к содержимому для бэкенда;
- код сложнее для анализа.



html:
<div class="frame">
    <div onclick="CtxInlineController.open(this)" class="menu-target ">
        <div class="ctx-wrapper">
            <div  style="display:none;"  class="ctx-menu" onmouseout="CtxInlineController.out(this,event)" onclick="event.stopPropagation()">
                <span class="line" onclick="alert('12');CtxInlineController.closeCtx(this)">123456</span>
                <span class="line" onclick="alert('ab');CtxInlineController.closeCtx(this)">abcdef</span>
            </div>
        </div>
    </div>
</div>

css:
.frame{
    position:relative;
    width:200px; 
    height:100px;
    border: 1px solid #666;
}
.line{
    display: block;
    width:100%;
    font-size:11px;
    line-height: 16px;
    cursor:pointer;
}

.menu-target{
    position:absolute;
    right:5px;
    top:5px;
    width:24px;
    height:24px;
    border:1px solid #666;
    background-color: #CCC;
}
.menu-target:hover{
    background-color: yellow;
}
.ctx-wrapper{
    position: relative;
    width:100%;
    height:0;
}

.ctx-menu{
    position:absolute;
    top:25px;
    right:-1px; 

    background-color:rgba(0,0,0,.7);
    color:white;
    width:166px;
    margin-top:50px;
    opacity: 0;
    -webkit-transition: all 0.3s ease-in-out;
    -moz-transition: all 0.3s ease-in-out;
    -ms-transition: all 0.3s ease-in-out;
    transition: all 0.3s ease-in-out;
}
.ctx-menu-behavior{
    margin-top:0;
    opacity: 1;
}


js:
var CtxInlineController = {
    // Const for configure:
    MENU_SELECTOR:".ctx-menu",
    TRANSITION_CLASS:"ctx-menu-behavior",
    // Inline methods:
    // @memberOf CtxInlineController - onclick handler
    // @param {HtmlElement} self - target element
    open:function(self){
        var     $self = $(self),
                ctxMenu = $self.find(this.MENU_SELECTOR);
        
        if(ctxMenu.css("display")=="none"){
            ctxMenu.removeClass(this.TRANSITION_CLASS);
            ctxMenu.show();
            ctxMenu.addClass(this.TRANSITION_CLASS);
        }else{
            ctxMenu.removeClass(this.TRANSITION_CLASS);
            setTimeout(function(){
                ctxMenu.css("display","none");
            },300);
        }
    },
    // @memberOf CtxInlineController - onmouseout handler
    // @param {HtmlElement} self - target element
    // @param {MouseEvent} event
    out:function(self,event){
        var e = event.toElement || event.relatedTarget;
        if (e.parentNode == self ||  e == self) {
            return;
        }
        if(self.style.display!='none'){
            $(self).removeClass(this.TRANSITION_CLASS);
            setTimeout(function(){
                self.style.display='none';
            },300);
        }
    },
    // @memberOf CtxInlineController api for closing menu
    // @param {HtmlElement} self - target element
    closeCtx:function(self){
        var ctxMenu = $(self).closest(this.MENU_SELECTOR);
        
        ctxMenu.removeClass(this.TRANSITION_CLASS);
        setTimeout(function(){
            ctxMenu.css("display","none");
        },300);

    }
};

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

16 июня 2013 г.

Реализация двухстороннего флипа картинки

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


Работающее демо так же доступно по ссылке.

Весь механизм выполняется с помощью css3. Две картинки вращаются вокруг одной оси с одинаковой скоростью, но с фазовым сдвигом периода вращения 180 градусов (От -180 до 0 градусов и от 0 до 180 градусов). Кого физика сбила столку могут ознакомится с исходниками.

HTML:
<div class="rotor">
    <div id='curr' class="rotor-flip" style="background-image: url(/img/image.jpg);"></div>
    <div id='next' class="rotor-flip next" styLe="background-image: url(/img/next.jpg);"></div>
</div>
<button onclick="rotor()" class="m12">Flip</button>
CSS:
.rotor{
    width:180px;
    height:180px;
    position: relative;
    -webkit-perspective:1400px;
    -moz-perspective:1400px;
    -o-perspective:1400px;
    perspective:1400px;
}
.rotor>.rotor-flip{
    position: absolute;
    width:100%;
    height:100%;
    -webkit-backface-visibility:hidden;
    -webkit-transition:-webkit-transform .6s cubic-bezier(0.165, 0.84, 0.44, 1);
    -moz-backface-visibility:hidden;
    -moz-transition:-moz-transform .6s cubic-bezier(0.165, 0.84, 0.44, 1);
    -o-backface-visibility:hidden;
    -o-transition:-o-transform .6s cubic-bezier(0.165, 0.84, 0.44, 1);
    backface-visibility:hidden;
    transition:transform .6s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.next{
    -webkit-transform:rotateY(-180deg);
    -moz-transform:rotateY(-180deg);
    -o-transform:rotateY(-180deg);
    transform:rotateY(-180deg);
}
.flipped {
    -webkit-transform:rotateY(180deg);
    -moz-transform:rotateY(180deg);
    -o-transform:rotateY(180deg);
    webkit-transform:rotateY(180deg);
}
JS:
function rotor(){
    var     curr = document.getElementById('curr'),
            next = document.getElementById('next');

    if(curr.className == "rotor-flip"){
        curr.className = 'rotor-flip flipped';
        next.className = 'rotor-flip';
    }else{
        curr.className = 'rotor-flip';
        next.className = 'rotor-flip next';
    }           
}

12 мая 2013 г.

node.js express и шаблонизатор underscore


Так как уже имеется сайт с работающим underscore-вским шаблонизатором то писать пост приходится не на пустом месте.
По этому все замеченные мною детали, которых не указаны в документации к uinexpress или underscore я объединил в этом посте.

Почему express и underscore.
Простая реализация шаблонизатора. Вся логика реализуется на javascript-е в обычной html верстке. В шаблонах имеется доступ к методам библиотеки underscore (но это на любителя underscore). Если вы никогда им не пользовались, то я могу сказать, что все конструкции на js нужно помещать в конструкции <% ... print(variable_name); %> или <%-variable_name%>, <%=variable_name %>.

Устанавливается с пакетом uinexpress через npm. Версия которую я использовал 0.05. По номеру версии видно, что пакет совсем "маленький".

Добавление параметров в шаблон
Ситуация такая, имеется layout файл:
<!DOCTYPE html>
<html>
<head>
    <title><%=$title%></title>
</head>
<body><%=body%></body>
</html>

По умолчанию основной текст представления подставляется в шаблон на место переменной body. Но иногда требуется чтобы в шаблоне подставлялись и другие значения, например значения для meta-тегов.
Соответственно значения необходимых параметров можно задать:
  1. в настройках роутинга
  2. в самом представлении
В настройках роутинга:
// Так можно задать дефолтные параметры в шаблон
var defaultOptions={
    $title:"",
    $description:"",
    $keywords:""
};
app.get('/', function(req, res){         
    res.render('index.html', defaultOptions);
});
В самом представлении:
<% $title="simple page" %>
Правда если в шаблоне обнаружится что переменная нигде не было про инициализирована, то приложение выбросит exception. Поэтому в шаблоне может пригодится и такой код:
<% if(typeof $title != "undefined"){ %>
    <title><%=$title%></title>
<% } %> 

Задание дефолтного шаблона
Пакет uinexpress в качестве дефолтного шаблона использует файл layout  с указанным в настройках расширением и адресом папки с представлениями.

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

Partials
С этим в uinexpess плохо. Partials не реализованы и вообще как нибудь их реализовать в шаблонном движке underscore проблематично.
Так как во время компиляции шаблона невозможно подставить значение из асинхронной callback функции. Именно это будет если считывать файлы из файловой системы. Возможный вариант решения, который мне видится - это при запуске приложения заносить в кэш uinexpress все необходимые partials а уже в самом шаблоне извлекать их значения.