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 а уже в самом шаблоне извлекать их значения.

4 февраля 2013 г.

IE следит за твоей мышкой...


IE следит за твоей мышкой...
Нашел дивную серию статей про уязвимость Internet Explorer:
Что-то на подробнее вот этого:
"Пользователи веб-браузера Internet Explorer оказались в щекотливой ситуации: согласно данным западных экспертов в области кибер-безопасности, уязвимость, скрывающаяся в интерпретаторе javascript данного браузера, позволяет злоумышленникам отслеживать движения курсора мыши удаленного пользователя.
Уязвимости подвержены версии Internet Explorer с 6 по 10. А считывание позиции курсора мыши происходит даже при неактивном окне веб-браузера, что ставит под удар пользователей банковских продуктов. Также можно говорить об угрозах для тех, кто использует виртуальные клавиатуры, включая и некоторых пользователей планшетов.
Между тем, корпорация Microsoft уже начала расследование инцидента и, вероятно, в скором времени выпустит патч.
Примечательно, что помимо злоумышленников, данной уязвимостью могут активно пользоваться рекламные компании, осуществляющие рекламу в Сети."
Так сильно удивился эпичности содержания решил проверить эту самую уязвимость.
<div id="detector"></div>
window.attachEvent("onload",function(){
    var     det = document.getElementById("detector");
    det.attachEvent("onmousemove",function(e){
        det.innerHTML = e.screenX + ", " + e.screenY;
    });
    setInterval(function(){
        det.fireEvent("onmousemove");
    },100);
});
Можно использовать и другие события не только mousemove.
Доступны только значения:
e.altKey {Bool} - состояние [ALT]
e.altLeft {Bool} - [ALT Left]
e.altKey {Bool} - состояние [CTRL]
e.altLeft {Bool} - [CTRL Left]
e.clientX {Int} - координаты мыши относительно просматриваемой области документа
e.clientY {Int}
e.offsetX {Int}
e.offsetY {Int}
e.screenX {Int} - координаты мыши относительно верхнего левого угла экрана (0,0) - верхний левый угол экрана
e.screenY {Int}
e.x {Int} - координаты относительно родительского компонента
e.y {Int}

Что нельзя узнать:
1) А был ли клик
2) какую клавишу нажали кроме (Alt,Schift, Ctrl)
3) правую или левую кнопку мыши нажали
Т.е. нужны еще уязвимости или доп. информация о происходящем на десктопе, что бы сделать какой-нибудь вывод.

Мощная уязвимость?