30 марта 2012 г.

Поле для форматированного ввода числа

Вступление
Вам никогда не требовалось контролировать пользователя при вводе данных в форму на сайте? На этой недели такую задачу я получил на работе. Имелось поле для ввода ссумы. Но чтобы ввод был форматированным не "100000", а "100 000". И соответстенно только цифры.

Что нам Google подскажет
Как Google(Yandex) не борется за хороший поиск, а оригинальные варианты он не отбирает. Получается список почти идентичных вариантов контроля ввода через событие keypress. Html5 тоже не помог type="number" даже не контролирует ввод только цифр. Не много отвлекусь, поиск по блогам Google или Яндекса частично помогает разрядить выдачу однотипных решений. Но все равно чтобы найти хороший метод в выдаче нужно постараться.

Что нам понадобится
В стартное html поле ввода input можно ввести данные тремя спосбами:
  1. с клавиатуры. Это можно отловить слушая события keypress, keydown, keyup;
  2. вставить из буфера обмена;
  3. автоподстановка в браузере. 
Почти все найденные мной в поисковике решения борятся с первым способом, неучитывая два других. Для борьбы с вставкой из буфера обмена я воспользовался JQuery плагином jQueryTextChange (ссылка1, ссылка2).  Для борьбы с автоподстановкой в тег input можно добавить атрибут autocomplete со значением off:
<input autocomplete="off" autofocus="" id="principal_amount" name="principal_amount" required type="text">
Как это выглядит
Представляю свое решение
if (!Array.prototype.indexOf){// Привет IE
    Array.prototype.indexOf = function(elt/*, from*/){
        var len = this.length,
            from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0) from += len;
        for (; from < len; from++){
            if (from in this && this[from] === elt)
                return from;
        }
        return -1;
    };
}
function numberFormat(str){ // форматируем строку 12000 -> 12 000
    var     str_array=[],
            length=str.length;
    var undefined2string=function(val){
        return val?val:"";
    }
    for(var i=length-1; i>=0;i-=3){
        str_array.unshift(
            undefined2string(str[i-2])+undefined2string(str[i-1])+undefined2string(str[i])
        );
    }
    var resStr=str_array.join(" ");
    return resStr;
}
function createFormatControl(element,onchange_callback){
    if (!(element instanceof HTMLInputElement))
        throw("Wrong type of node, need HTMLInputElement");
        
    onchange_callback=onchange_callback||function(){};
    /*
    @e.keyCode:
        9 - Tab
        8 - Backspace
        46 - Del
        48-57 - 0-9
        13 - Enter
        37-40 - arrow: left,top,right,bottom
        35 -Home
        36 -End
    */
    $(element).bind("keydown",function(e){//контролируем ввод с клавиатуры
        var     keyCodeArray=[8,9,13,46];
        if(
            (e.keyCode>47 &&  e.keyCode<58) ||
            keyCodeArray.indexOf(e.keyCode) >= 0 ||
            (e.keyCode>34 &&  e.keyCode<41)
        ){
            return true;
        }else{
            return false;
        }
    }).bind("textchange",function(e){// Контролируем изменение текста (вставку из буфера обмена.)
    
        var     $this=$(this),
                origStr=$this.val().replace(/\D+/g,""),
                str=numberFormat(origStr);
        if(!(e.keyCode>34 &&  e.keyCode<41)){
            $this.val(str);
        }
        onchange_callback(str);
    });
}
$(document).ready(function(){
    var     in2 = document.getElementById("in2"),
            $deb3=$("#deb3");
    createFormatControl(in2,function(e){$deb3.text(e);});
});
Верстка:
<script type="text/javascript" src="jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="jquery.textchange.min.js"></script>
<p>
    <label>#2: </label>
    <input type="text" id="in2" autocomplete="off"/>
</p>
<p id="deb3"></p>

Скачать архив с исходниками

19 февраля 2012 г.

Как составить график в Excel по отдельным точкам из диапазона


На все запросы Google и Yandex дружно поднимают всякий шлак на тему «Как легко вставить график».
Все оказалось достаточно просто нужно лишь перечислить необходимые точки через точку запятую («;»).
Ниже на скриншотах приведены диалоговые окна Excel 2007. Но для OpenOffice/LibreOffice Calc все делается аналогично.
Оригинальный график из которого надо оставить несколько точек
Выбираем данные.
Диалоговое окно в котором можно указать необходимые точки

Значение поля «Диапазон данных для диаграммы»:
=Позиции_по_недельно!$B$2:$AC$2;Позиции_по_недельно!$B$4:$AC$4
Заменяем на:
=Позиции_по_недельно!$B$2:$D$2;Позиции_по_недельно!$I$2:$I$2;Позиции_по_недельно!$M$2:$M$2;Позиции_по_недельно!$R$2:$R$2;Позиции_по_недельно!$W$2:$W$2;Позиции_по_недельно!$AC$2:$AC$2;Позиции_по_недельно!$B$4:$D$4;Позиции_по_недельно!$I$4:$I$4;Позиции_по_недельно!$M$4:$M$4;Позиции_по_недельно!$R$4:$R$4;Позиции_по_недельно!$W$4:$W$4;Позиции_по_недельно!$AC$4:$AC$4

В колонке B – подпись ряда, в колонке D начинаются собственно данные.

Результат



Итоги конкурса сайтов банков Санкт-Петербург

В Санкт-Петербурге, где я живу и работаю, существует организация "Северо западная ассоциация банков" (www. nwab.ru), которая ежегодно проводит конкурс банковских сайтов.  В конкурсе участвуют только банки состоящие в этой ассоциации. Так как начальство направляет меня туда в качестве члена жюри, то в моих руках оказались итоги конкурса за 2010, 2011 годы.

В следующей таблице приведены итоги конкурса за 2011 год и для сравнения итоги за 2010 год.

Занятое место в 2011 Банк Занятое место в 2010 Изменение по сравнению с 2010
1 Альфа-Банк 1 0
2 Райффайзенбанк 17 +15
ЮниКредит Банк 6 +4
3 Северо-Западный банк Сберегательного банка России 19 +16
4 Росбанк 21 +17
5 Абсолют Банк 8 +3
Открытие 9 +4
ЮНИАСТРУМ БАНК 5 0
6 Русский Стандарт 13 +7
7 Банк БФА 63 +56
Московский Банк Реконструкции и Развития 2 -5
8 ГЛОБЭКС 10 +2
КИТ Финанс 22 +14
9 НОМОС-БАНК 15 +6
10 Ханты-Мансийский банк 24 +14
11 Связь-Банк 23 +12
12 Газпромбанк 3 -9
13 ПРОМСВЯЗЬБАНК 18 +5
14 Агросоюз
15 АГРОПРОМКРЕДИТ 4 -11
16 ВТБ 24 7 -9
17 Инвестбанк 17 0
18 БАЛТИНВЕСТБАНК 39 +21
19 МДМ Банк 10 -9
20 Национальный Залоговый Банк 46 +26
Национальный Резервный Банк 38 +18
21 Мираф-Банк 57 +36
22 Банк "Санкт-Петербург" 10 -12
Стройкредит 20 -2
Энергомашбанк 26 +4
23 ГОРОД 14 -9
24 Мастер-Банк 27 +3
25 Сведбанк 32 +7
26 РОССИЯ 40 +14
27 МОСОБЛБАНК 33 +6
28 Ланта-Банк 44 +16
29 ВТБ, Северо-Западный региональный центр 12 -17
30 Витабанк 25 -5
31 Огни Москвы
32 Кредит-Москва 37 +5
33 Охотный ряд 32 -1
34 ЗЕНИТ 34 0
Констанс-Банк 56 +22
35 Александровский 53 +18
36 МБА-Москва 35 -1
37 Севзапинвестпромбанк 48 +11
38 Петербургский Городской Банк 41 +3
39 ПромСервисБанк 49 +10
Эллипс банк
40 СЕВЕРГАЗБАНК 60 +20
41 Советский 36 -5
Экономический Союз 45 +4
42 СИАБ 31 -11
43 Адмиралтейский 42 -1
Балтика
44 Таврический 51 +7
45 Объединенный капитал 65 +20
46 Международный банк Санкт-Петербурга 55 +9
47 МОРСКОЙ БАНК 47 0
Рускобанк 57 +10
48 Тетраполис 52 +4
49 Викинг 59 +10
50 Невский банк 66 +16
51 Финансовый капитал 70 +19
52 Москомприватбанк 64 +12
53 Национальный Торговый Банк 43 -10
54 Банкирский Дом 67 +13
СЭБ Банк 58 +4
55 Северо-Западный 1 Альянс Банк
56 Инвест-Экобанк 72 +16
57 Санкт-Петербургский банк инвестиций 71 +14
58 Югра 69 +11
59 ЭКСИ-Банк 75 +16
60 НЕВАСТРОЙИНВЕСТ 74 +14
Банки которые участвовали в 2010, но  не приняли участие в 2011 году:
БИНБАНК - 11, АМТ-Банк - 16, Сосьете Женераль Восток - 28, Первый Республиканский Банк - 29, Эйч-Си-Би-Си Банк - 30, Балтийский Банк - 32, Алемар - 50, СИББИЗНЕСБАНК - 61, КОММЕРЦБАНК (ЕВРАЗИЯ) - 62, БНП Париба Восток - 68, Кредит Свис - 73.

Небольшие замечания к результатам:
  • В 2011 при определение мест учитывался  средний бал по всем параметрам оценки сайта (и видимо было округление), в то время как в 2010 все балы суммировались. Возможно этим можно объяснить большое количество банков на одних и тех же местах;
  • Некоторые изменения в позициях у некоторых банков можно объяснить уменьшением количества участников.
Что здесь можно сказать:
  • Сайты студии Артемия Лебедева в фаворитах (Альфа банк и Русский Стандарт);
  • Банки которые в 2011 году вложились в свои сайты довольно существенно поднялись по таблице - Банк БФА с 63 до 7-го, Росбанк с 21 до  4 и т. д.

12 февраля 2012 г.

Регулярные выражения в Haskell

Предыстория.
Где-то на отрезке времени длиной в пол года я экспериментировал с использованием регулярных выражений в Haskell. Потому что хотел использовать для написание небольших утилит, где как раз требовалось применение регулярных выражений. В конечном итоге сделал все на Ruby.

Вступление.
И как оказалось в Haskell нет единой библиотеки для поддержки регулярных выражений. Так обилие выбора создала проблему с выбором. Да и документации по каждой из библиотеке оказалось не так и много. Что и подтолкнула меня к написанию этого поста.
Примечания:
1) Я не буду приводить примеры типа "Hello world": поиск количества совпадение, захват фрагмента. В место этого приведу готовый листинг кода с основыными замечанными особенностями.
2) Под скальпель попали:
  • regex-pcre (0.94.2)
  • pcre-light (0.4)
  • regex-posix (0.95.1)
3) На производительность библиотек я не смотрел. Так как это было для меня вторичным.
То на что я обращал внимание:
- корректная обработка unicode и кириллицы (как оказалось это актуально!)
- возможность обработки строк (замена, разбитие на фрагменты)
4) Мои выводы могут быть неправильными, так как у меня не было подробной документации/примеров кода по каждой библиотеке.
Акт 1. На сцене regex-pcre
-- Пример  на тестирование Regex.PCRE
import qualified Text.Regex.PCRE as PCRE
{-
regex-pcre  v 0.94.2
"+" 
1) вроде как бы поддерживает PCRE нотацию
"-"
1) С некоторыми русскими буквам не работает ('Ф', 'Ъ' и др.)
2) Возможности для обработки строк (замена, разбитие) не нашел
-}
main = do
    putStrLn "START:"
    -- работает
    putStrLn "#0:"
    print $ testPCRE "Name: ([А-Яа-я\\s]+) Ser" "Name: ЧьетоИмя Ser" 
    -- работает
    putStrLn "#1:"
    print $ testPCRE "имя:\r\n([А-Яа-я\\s]+)\r\nфамилия" "имя:\r\nФЫВв \r\nфамилия" 
    -- работает
    putStrLn "#2:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nфамилия" "Имя:\r\nФЫВв \r\nфамилия" 
    -- работает
    putStrLn "#3:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nфАмилия" "Имя:\r\nФЫВв \r\nфАмилия" 
    -- не работает (Не любит Regex.Posix букву "Ф")
    putStrLn "#4:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nФамилия" "Имя:\r\nФЫВв \r\nФамилия" 
    -- Заменяем "Ф" на "[Ф]" и работает:
    putStrLn "#5:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\n[Ф]амилия" "Имя:\r\nФЫВв \r\nФамилия" 
    
    print $ let regex = "имя:(.*?)фамилия" ; match = "имя:\r\nПетр \r\nфамилия" PCRE.=~ regex :: [[String]] in match
    print $ let m= "xyz abc more text" PCRE.=~ "(\\w+) \\w+" ::[[String]] in m
    putStrLn "#6 Поиск email: "
    -- Тут все нормально
    print $ testPCRE "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "aaaa \r\nlesprom.spb@bk.ru\r\n bbbb" 
    -- А вот здесь текст захватвается паттерном!
    print $ testPCRE "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "регистрация"


testPCRE regex text = let match = text PCRE.=~ regex :: [[String]] in match


Оранжевым подсвечены ошибки.
Как оказалось самая худшая из всех тестируемых:
  • В документации были намеки на то что имеется возможность "настройки" поиска (нечувствительность регистра, точка - включает себя символ конца строки и т. п.) - но как это сделать не понятно. 
  •  Явная проблема с кириллицей. Были случаи ложного захвата в тексте на русском.
Акт 2. На сцене pcre-light
-- Пример  на тестирование Regex.PCRE.Light
import Text.Regex.PCRE.Light
import qualified Data.ByteString.UTF8 as B
{-
pcre-light v 0.4
"+":
1)шаблон регулярных выражений c PCRE нотацией
2)unicode и как следствие адекватная работа с кириллицей
"-"
1) Нет возможности работы с строками (замена, разбитие)
-}
main = do
    putStrLn $ B.toString  $ B.fromString "START:"
    print $ let r = compile (B.fromString "(\\s[фыва]+\\s)") [] in (match r (B.fromString "aaddd фыва dd") [])
    print $ testPCRELight "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "aaaa \r\nlesprom.spb@bk.ru\r\n bbbb" 
    print $ testPCRELight "\\b([\\w.\\-\\d]+@[\\w.\\-\\d]+)\\b" "регистрация"

testPCRELight :: String -> String -> Maybe [B.ByteString]
testPCRELight pattern str = let r = compile (B.fromString pattern) [] in (match r (B.fromString str) [])

-- Могут пригодится:
func1 :: Maybe [String] -> String
func1 (Just x) = last x
func1 Nothing = " - "

func2 :: Maybe [B.ByteString] -> B.ByteString
func2 (Just x) = last x
func2 Nothing = B.fromString " - "

Хорошая PCRE библиотека:
  1. PCRE и кириллица в норме.
  2. Нет возможности работы с строками (замена, разбитие). 
Акт 3. На сцене Нет возможности работы с строками (замена, разбитие)
-- Пример  на тестирование Regex.posix
import Text.Regex.Posix
import Text.Regex --Regular expression matching. Uses the POSIX regular expression interface in Text.Regex.Posix.
-- <) http://hackage.haskell.org/packages/archive/regex-compat/0.95.1/doc/html/Text-Regex.html
{-
regex-posix v. 0.95.1
Особенности:
1) Это Posix а не Perl нотация регулярных выражений,  по этому:
- нет никаких \w, \d, \s символьных классов
- нет ленивых квантификаторов (.*?), только жадные
2) Есть возможность замены в строковых переменных по posix регулярным выражениям
-}
main = do
    putStrLn $ "START:"
    putStrLn $ "exp #0:"
    print $ testCountMatches "12, 34, 78" 
    print $ testReplace "12 moon"
    putStrLn $ "exp #1:"
    print $ removeByPattern " /*Hel\r\nl122o*/ 13 friday " "(/\\*.*\\*/)"
    putStrLn $ "exp #2:" -- Posix видимо использует только жадные квантификаторы
    print $ removeByPattern " /*Hel\r\nl122o*/ 13 friday /* ddd*/ dddd" "/\\*.*?\\*/"
    
testCountMatches :: String -> Int
testCountMatches inPut = inPut =~ "([0-9]+)" :: Int

testReplace :: String -> String
testReplace input = subRegex (mkRegex "([0-9]+)") input "White"

-- mkRegexWithOpts() parameters:
-- False -> meen that '.' include also "\n" character!
-- True -> meen that Case sensitive!
removeByPattern :: String -> String -> String
removeByPattern input pattern = subRegex (mkRegexWithOpts pattern False True) input ""

Все хорошо только PCRE все же не хватает.
Имеется возможность работы замены в строке, разбитие строк.

Заключение. 
Все течет. Все меняется. Надеюсь, с библиотеками так же.

12 января 2012 г.

Битрикс головоломка


В предыдущем посте посвященном ЧПУ Битрикс, был задан вопрос такого содержания:
Скажем есть инфоблок КАТАЛОГ, в нем раздел ТЕЛЕФОНЫ, в нем подраздел НОКИЯ, вот как в обработке адресов правильно написать условие, если мы не используем стандартные битриксовые убогие айди-чпу для комплексного компонента?

Единственное, что мне пока пришло в голову - это использовать уникальный символьный код каждого отдельного товара.

Надеюсь Вы поймете мой ход мыслей и если что подправите.
Допустим у нас такая иерархия:
КАТАЛОГ (тип инфоблока: katalog)
\_раздел ТЕЛЕФОНЫ ( инфоблок: phone)
| \_ подраздел НОКИЯ (символьный код раздела: nokia_phone)
| | \_ телефон1 (символьный код элемента: code1)
| | |_ телефон2 (code2)
| |- подраздел САМСУНГ (символьный код раздела: samsyng_phone)
| | \_ телефон3 (code3)
| | |_ телефон4 (code4)
| |...

Подразделы в разделах Инфоблоках похоже можно использовать только в декоративных целях.
Для вывода элементов подраздела в таких компонентах как компонент "Список новостей", ну или только  использовать его символьный код ЧПУ.

Каждый элемент инфоблока для использования его #CODE# в ЧПУ должен содержать уникальный символьный код. Как настроить всю вязанку компонентов Битрикс  для учета и символьного кода раздела я еще не представляю.. 

Каталог Мобильных телефонов.
В параметрах компонентах, в поле URL страницы детального просмотра можно указать ЧПУ адрес вида:
/mobile/#SECTION_CODE#/#CODE#/
где #SECTION_CODE# - поле подстановки "символьный код раздела"
#CODE# поле подстановки "символьный код элемента"

В обработку адресов можно добавить следующую запись.
Условие:
#^/mobile/([\w\d\_]+)/([\w\d\_]+)/#
Файл: 
указать url адрес на страницу с компонентом детального просмотра
Правило:
SECTION_CODE=$1&CODE=$2

Итого:
Имеется url адрес /mobile/nokia_phone/code2/, где смысловую нагрузку несет только code2

10 января 2012 г.

Клонирование объектов Java-script


Парадоксальная ситуация Javascript объектно ориентированный язык программирование, но в нем нет встроенных механизмов для клонирования объектов. Чехарда с передачей ссылок на объекты "деревенским" присваиванием всеже не всегда нужна.
Когда мне понадобился рабочий код для копирования объектов, то я довольно быстро нашел по блогам готовые решения.
Но в дальнейшем, под действием ряда факторов его пришлось модифицировать.
Поэтому представляю свой метод-функцию копирования объектов java-script:

// notUseRecursion - опция копирования свойств объекта без использования рекурсии (сохраняются ссылки).
// Полное копирование объекта события в IE это его смерть от выхода за пределы памяти.
// Поэтому есть смысл его отключить
function clone(o,notUseRecursion) {// Out of the memory in IE8
    if(!o || 'object' !== typeof o)  return o;
    var     c = 'function' === typeof o.pop ? [] : {},
            p, v;
    for(p in o) {
        //if(o.hasOwnProperty(p)) {// Dont supported in IE8
        if (Object.prototype.hasOwnProperty.call(o,p)){
            v = o[p];
            c[p] = (v && 'object' === typeof v && !notUseRecursion)?clone(v):v;
        }
    } 
    return c;
}

Основные особенности, которые пришлось учесть в коде функции клонирования - это перлы Internet Explorer 6-8:
1) Свойство объекта hasOwnProperty() не поддерживается, в место него происходит применение одноименной функции прототипа объекта Object (см. код).
2) Рекурсивное копирование объекта события (window.event к примеру) - приводило к остановке выполнения скрипта от "out of the memory". Объект события содержит методы "нативного кода" (Native code) который лучше не копировать, а сохранять ссылки на них. Поэтому есть смысл скопировать объект без использования рекурсии (notUseRecursion=yes).
Если найду еще какие-нибудь препятствия на пути свободного копирования объектов, обязательно опубликую их в блоге...

9 января 2012 г.

Snap - Haskell web framework

Snap прежде всего первый веб фреймворк на Haskell с которым я познакомился и в общем, этот пост основан на том опыте который я получил тестируя этот фреймворк.
C помощью Snap я создал небольшой демонстрационный проект, который я представляю по ходу текста. Местами в тексте будет встречаться дословный перевод документации.
Официальный сайт
Погружение в Snap

Установка snap через cabal

#: sudo cabal update
#: sudo cabal install snap

Настройки параметров системы Ubuntu

Snap хранит свои исполняемые бинарные файлы в папке ~/.cabal/bin (snap к примеру), по этому для удобства их использования в файл ~/.bashrc следует добавить строчку:
PATH=$HOME/.cabal/bin:$PATH

Создание каркаса типового Snap проекта

#: mkdir myproject
#: cd myproject
#: snap init

команда snap init создает шаблон типового проекта в текущей директории.
Сборка проекта:
$: sudo cabal install
Запуск:
#: myproject -p 8000
Теперь по адресу  http://localhost:8000/ должен откликаться созданый сайт.

Файловая структура созданного проекта

dist/
log/
resources/
static/
templates/
src/
Site.hs
Main.hs
Application.hs
Попробую объяснить назначение файлов в папке src
Файл main.hs - это скелет проекта. В файле Application.hs определяется модуль Application, который связан с обработкой ответов сервера и который используется в файле Site.hs. Application.hs используется для расширения стандартного Snap обработчика HTTP запросов. Site.hs - это основной исходный код проекта, роутинг, контроллеры и т.п.
Обработка url адресов в файле Site.hs задается следующим образом:
site :: Application ()
site = route [ ("/",            index)
             , ("/echo/:stuff", echo)
             ]
       <|> serveDirectory "resources/static"

Url адресс "/" относится к функции-обработчику index, "/echo/:stuff" к функции-обработчику echo. Обе эти функции выполняют роли контроллеров.
Фрагмент url адреса отмеченный двоеточием (":") - захватываемое значение параметра stuff. Значение этого параметра можно получить в дальнейшем с помощью rqParams или getParam (как и параметры передаваемые через POST и GET методы).
Вторая половина отделенная от основной части "<|>" будет переправлять на содержимое папки resources/static/.
Дословно оператор "<|>" в монаде Snap означает "try a, and if it fails, try b".
К примеру файл style.css будет искаться в resources/static/. Если он там не найдется метод serveDirectory вынудит Snap выдать ответ "404 Not Found".
Функция/метод/обработчик echo выглядит следующим образом
echo :: Handler App App ()
echo = do
    message <- decodedParam "stuff"
    heistLocal (bindString "message" (T.decodeUtf8 message)) $ render "echo"
  where
    decodedParam p = fromMaybe "" <$> getParam p

Здесь функция decodedParam выступает в качестве обёртки над методом  getParam. В качестве аргумента передается ей имя параметра заданного в роутинге ("stuff"), но можно и передать и имя параметра post при необходимости.
С помощью метода heistLocal используем  полученное значение для построения страницы с помощью шаблона "echo" (render "echo").
А при помощи метода bindString подменяем параметр в шаблоне <message/> на значение (T.decodeUtf8 message).

Snap использует систему шаблонизации Heist. Довольно гибкая и на оф. сайте представлено  достаточно документации.
В предыдущем примере указанный шаблон echo располагается в файле resources/templates/echo.tpl, который представляет из себя html файл с расставленными полями подстановки/параметрами.
Поля подстановки имеют вид ${key} или <key/>
Вот так выглядит шаблон echo:
<html>
<body>
  <div id="content">
    <h1>Is there an echo in here?</h1>
  </div>
  <p>You wanted me to say this?</p>
  <p>"<message/>"</p>
  <p><a href="/">Return</a></p>
</body>
</html>
Усложним типовой проект.
Добавим на главную страницу ("/") форму, которая при отправке данных будет:
  1. сохранять данные в файле -> Вызов монады IO в монаде Snap 
  2. выдавать страницу ответа -> Передача на страницу данных методом POST 
Добавим в файл Site.hs:
import System.IO
import qualified Data.ByteString as BS
...
-- пример на обработку post параметров ($_POST[]) и вызова монады IO из монады Snap (Понадобится чтобы сохранить данные в файл)
command3post :: Handler App App ()
command3post = do
    input_name <- decodedPost "firstname" -- ByteString
    second_name <- decodedPost "secondname"
    liftIO $ appendFile "output.txt" (((T.unpack . T.decodeUtf8. BS.concat ) [input_name, " : " ,second_name])++"\n")
-- Пояснение справа на лево:
-- BS.concat - слияние списка элементов ByteString
-- T.unpack . T.decodeUtf8 - преобразуем ByteString в String
-- appendFile - добавляет строку в файл
-- liftIO - преобразует монаду IO в монаду Snap
    heistLocal (bindString "firstname" (T.decodeUtf8 input_name)) $ render "formrezult"
    where
        decodedPost p = fromMaybe "" <$> getParam p 
А список маршрутов приведем к следующему виду:
routes :: [(ByteString, Handler App App ())]
routes = [ ("/",            index)
         , ("formaction", command3post)
         , ("/echo/:stuff", echo)
         , ("", with heist heistServe)
         , ("", serveDirectory "resources/static")
         ] 
В файл resources/templates/index.tpl добавить
<form method="post" action="formaction">
    <p>First name: <input type="text" name="firstname"/></p>
    <p>Second name: <input type="text" name="secondname"/></p>
    <input type="submit" value="send"/>
</form> 
Создать файл resources/templates/formrezult.tpl
<!DOCTYPE HTML>
<html>
  <head>
    <title>Form request Page</title>
  </head>
  <body>
    <div id="content">
      <h1>Form Rezult</h1>
    </div>
    <p>Thank you <firstname/></p>
    <p><a href="/">Return on main page</a></p>
  </body>
</html>
Вот и все. Проект можно собирать и запускать.
Скачать проект целеком.

Вместо вывода

Snap фреймворк очень напоминает работу с RoR или ASP.Net MVC. Хотя я и не вижу явных преимуществ перед другими фреймворками как на Haskell так и на других ЯП, Snap может пригодиться хаскелоидам. В общем дело чисто вкусовых предпочтений аудитории.