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 может пригодиться хаскелоидам. В общем дело чисто вкусовых предпочтений аудитории.

23 декабря 2011 г.

Падающие снежинки на css3


В канун нового года на своем сайте хочется навести красоту и украсить "по новогоднему" некоторые страницы падающими снежинками.

Выбор инструментов реализации пал на css3 анимацию. И как не трудно догадаться у такого решения есть и положительные и отрицательные стороны.
К положительным сторонам можно отнести:
  • относительную простоту реализации;
  • и отсутствие необходимости в сторонних компонентов.
К минусам:
  • Поддерживает только FireFox и Chrome(Webkit) (Opera 11.5x не поддерживает css3 анимацию).
В интернете я нашел уже готовый демонстрационный пример http://natbat.net/code/clientside/css/snowflakes/, который я взял за основу.
Переделать мне его пришлось дважды:
  • Первый пример использует в качестве снежинок символы юникод.
  • Второй пример где используются спрайтовые картинки для снежинок.
Как показало тестирование на разных компьютерах, символы юникода в качестве снежинок - не самый лучший выбор. Так как на некоторых компьютерах вместо снежинок падали квадратики или иероглифы.
Пересказывать как сделаны падающие снежинки на css3 довольно скучно, поэтому ограничусь демонстрацией исходников:
Первый пример - Рабочий пример на jsfiddle
Второй пример - исходники на Googlcode

20 декабря 2011 г.

Интересные моменты настройки почтовой рассылки Битрикс

... Или как добавить ссылку на отписку от рассылки в письме

Если посмотреть шире на проблему, то можно задаться вопросом, как можно добавить параметры вида #param# в отсылаемые подписчикам почтовые сообщения. Так же как это делается в шаблонах почтовых сообщениях. Таким образом, в отсылаемое письмо мы сможем разместить не только ссылку на отписку от рассылки, которая для каждого подписчика должна быть уникальной, но и добавить другие параметры:
  • Имя, фамилию, отчество;
  • Код подтверждения подписчика;
  • Id подписчика;
  • и др. параметры.
В документации Битрикса я нашел страницу c описанием необходимого функционала http://dev.1c-bitrix.ru/user_help/service/subscribe/rubric_edit.php?print=Y .

В принципе в нем описан все необходимые действия, но я приведу свой пример реализации.
В файле /bitrix/php_interface/init.php храняться обработччики выполняющихся событий.
В этот файл необходимо добавить следующий код:
Copy Source | Copy HTML
  1. // регистрируем обработчик 
  2. AddEventHandler("subscribe", "BeforePostingSendMail", Array("CatchSubscriter", "beforePostingSendMailHandler"));
  3. class CatchSubscriter{
  4.     // создаем обработчик события "BeforePostingSendMail" 
  5.     function beforePostingSendMailHandler(&$arFields){
  6.         //$USER_NAME = "Подписчик"; 
  7.         //Попробуем найти подписчика. 
  8.         $rs = CSubscription::GetByEmail($arFields["EMAIL"]);
  9.         if($ar = $rs->Fetch()){
  10.             //echo "<p>[</p>";
  11.             //print_r($ar);
  12.             //echo "<p>]</p>";
  13.             $arFields["BODY"] = str_replace("#CONFIRM_CODE#", $ar["CONFIRM_CODE"], $arFields["BODY"]);
  14.             $arFields["BODY"] = str_replace("#ID#", $ar["ID"], $arFields["BODY"]);
  15.             $arFields["BODY"] = str_replace("#EMAIL#", $ar["EMAIL"], $arFields["BODY"]);
  16.         }
  17.         //$arFields["BODY"] = str_replace("#NAME#", $USER_NAME, $arFields["BODY"]); 
  18.         return $arFields;
  19.     }
  20. }
Здесь в функции AddEventHandler("subscribe", "BeforePostingSendMail", Array("CatchSubscriter", "beforePostingSendMailHandler"));
Первый аргумент — название  используемого модуля подписки и рассылок.
Второй аргумент — название события.
Третий аргумент — массив с параметрами обработчика события.

Так при возникновении события BeforePostingSendMail т. е перед самой отправкой письма извлекаются почтовый адрес и текст письма из переменной $arFields. Зная email на который отправляется письмо — устанавливаем личность получателя, его ID и т. п. Текст письма проверяем на наличие ключей заданного формата (#EMAIL#, #ID#), и тут же их подменяем на нужные значения. Не трудно догадаться, что стандартный письмо рассылки можно расширить и другими парметрами.

Чтобы проверить работу можно отправить рассылку с следующим содержанием:
Copy Source | Copy HTML
  1. <p>email: #EMAIL#</p>
  2. <p>id: #ID#</p>
  3. <p>confirm code: #CONFIRM_CODE#"</p>
  4. <a href="http://www.tetrapolis.ru/service/edit_subscribe.php?ID=#ID#&CONFIRM_CODE=#CONFIRM_CODE#">Сменить адрес подписки</a>
  5. <a href="http://www.tetrapolis.ru/service/edit_subscribe.php?ID=#ID#&CONFIRM_CODE=#CONFIRM_CODE#&action=unsubscribe">Отписаться</a>
Ссылки должны вести на страницу с компонентом редактирования рассылки.
Если Вас заинтересовал метод Csubscription::GetByEmail() то у меня есть пример на работу с ним:
Copy Source | Copy HTML
  1. <?
  2. if(CModule::IncludeModule("subscribe")){
  3.     class CatchSubscriter {
  4.         // создаем обработчик события "BeforePostingSendMail" 
  5.         function BeforePostingSendMailHandler(&$arFields){
  6.             //Попробуем найти подписчика. 
  7.             $rs = CSubscription::GetByEmail($arFields["EMAIL"]);
  8.             if($ar = $rs->Fetch()){
  9.                 echo "<p>[</p>";
  10.                 print_r($ar);// посмотрим какие еще параметры можно получить
  11.                 echo "<p>]</p>";
  12.                 $arFields["BODY"] = str_replace("#CONFIRM_CODE#", $ar["CONFIRM_CODE"], $arFields["BODY"]);
  13.                 $arFields["BODY"] = str_replace("#ID#", $ar["ID"], $arFields["BODY"]);
  14.                 $arFields["BODY"] = str_replace("#EMAIL#", $ar["EMAIL"], $arFields["BODY"]);
  15.             }
  16.             return $arFields;
  17.         }
  18.     }
  19.     $cs1= new CatchSubscriter();
  20.     $ar=array("EMAIL" => "vklad@tetrapolis.spb.ru", "BODY" => " body ");
  21.     $rez=$cs1->BeforePostingSendMailHandler($ar);
  22.     print_r($rez);
  23. }else{
  24.     echo "nothing to load";
  25. }
  26. ?>

6 ноября 2011 г.

"Unexpected token u" в Chrome


Столкнулся в Chrome (v14) с таким сообщением ошибки:
"Uncaught SyntaxError: Unexpected token u"
Проблема проста особенно для языков с динамической типизацией, на вход метода JSON.parse попала undefined.
Обычно такое сообщение возникает, когда в исходном тексте попадаются лишние символы. Но в данном примере стандартный метод просто решил выкинуть ошибку с невнятным описанием, вместо того, чтобы вернуть undefined. Баг в Chrome?

Ошибку можно с имитировать:
var demo1={field1:"a",field2:"b"};
var str=JSON.stringify(demo1);
console.log(str);
console.dir(JSON.parse(str)); // Ok
console.dir(JSON.parse(undefined)); // Exception

4 ноября 2011 г.

События keydown и keypress в Javascript



По работе в плотную встретился с двумя этими событиями, и хочу поделиться своим опытом работы с ними. Заодно приведу пример использования этих событий.
Дело осложнаятся тем, что нету стандарта для событий нажатий клавиш, и поэтому имеются рассхождения в работе в разных браузерах. События keydown и keypress в объекте события могут содержать несколько свойств, характеризующих нажатую клавишу:
  • charCode - это unicode значение символа связанного с нажатой клавишей;
  • keyCode - код клавиш;
  • which - идентичен charCode.
Для пояснения приведу пример - вводимые символы 'a' и 'A' будут иметь одинаковый keyCode, но разыный charCode.
Событие keyPress служит для обработки печатных клавиш (printable characters), т.е. те которые имеют печатные символы: A-Z, 0-9, Enter, Backspace.
Но есть один неприятный момент с этим событием - по своему назначению работет только в IE и Chrome(Webkit браузерах). В FireFox и Opera объект события возвращает коды  и клавиш с непечатными символами (Non printable characters) - клавиша Del, стрелочки. Причем коды для печатных и непечатных символов могут совпадать.
Событие keyDown - объект события "отлавливает" как коды печатных символов, так и не печатных символов. При этом код каждой клавиши будет уникальным.
Подробнее про эти два события можно почитать на  http://www.quirksmode.org/js/keys.html

Варианты значений свойств объекта события keypress для разных браузеров:
Chrome 15/Safari 5.1.1
Примечание: N- целое число не равное нулю.
Клавиши keyCode charCode which
Printable characters N N N
Non pintable characters - - -
Opera 11.52:
Клавиши keyCode charCode which
Printable characters N undefined N
Non pintable characters N undefined 0
FireFox 7:
Клавиши keyCode charCode which
Printable characters 0 N N
Non pintable characters N 0 0
IE6/7/8/9:
Клавиши keyCode charCode which
Printable characters N undefined undefined
Non pintable characters - - -

Привожу несколько примеров работы с событиями keypress и keydown.
Последний пример где производится проверка ввода - а именно в поле разрешается вводить только вещественное положительное число, имеет небольшой недостаток при использование. Текст можно ввести не только с клавиаткры, но и вставить из буфера обмена и подстановки самого браузера.
JS код:
Copy Source | Copy HTML
  1. window.onload=function(){
  2.     var tst1=id("tst1"),
  3.         dbg1=id("dbg1"),
  4.         tst2=id("tst2"),
  5.         dbg2=id("dbg2");
  6.     event(tst1,"keypress",function(e){
  7.         var nonPrintable,printable; // параметры с помощью которых попытаемся решить проблему с кросс браузерностью
  8.         if((e.charCode=== 0||e.charCode===undefined)&&e.which=== 0)
  9.             nonPrintable=e.keyCode;
  10.         printable=e.which?e.which:(e.which===undefined&&e.charCode===undefined)?e.keyCode:undefined;
  11.         dbg1.innerHTML="keyCode: "+e.keyCode+", charCode: "+e.charCode+", which: "+e.which+
  12.         " #### caf: "+printable+", yk: "+nonPrintable;
  13.     });
  14.     event(tst2,"keydown",function(e){
  15.         dbg2.innerHTML="keyCode: "+e.keyCode+", charCode: "+e.charCode+", which: "+e.which;
  16.     });
  17.     var num1=id("num1");
  18.     // тегу input добавлем проверку ввода
  19.     event(num1,"keydown",function(e){
  20.         e = (e)?e: (window.event)?window.event:undefined;
  21.         var target;
  22.         if(e) {
  23.             target = (e.target)?e.target:e.srcElement;
  24.         }
  25.         console.dir(e);
  26.         if (e.keyCode===13){// Enter if need
  27.             e.preventDefault? e.preventDefault() : e.returnValue = false;
  28.         }
  29.         if ((e.keyCode>47&&e.keyCode<58)||e.keyCode===8||e.keyCode===37||e.keyCode===39||e.keyCode==46){
  30.             e.returnValue=true; // [0..9] -> [47..58], 8 - backsace, 37 - Left, 39 - Right, 46 -Delete
  31.         }else if (e.keyCode===188 || e.keyCode===190){
  32.             var s=target.value, // input field!
  33.                     i= 0,count= 0;
  34.             while (s[i]){
  35.                 if (s[i]==='.' || s[i]===',')
  36.                     count++;
  37.                 i++;
  38.             }
  39.             e.returnValue=count=== 0;// Точка или запятая может быть только одна в водимом числе
  40.             if(count!== 0 && e.preventDefault)// Condition for FireFox
  41.                 e.preventDefault();
  42.         }else{
  43.             e.preventDefault? e.preventDefault() : e.returnValue = false;
  44.         }
  45.     });
  46. };
  47. function id(s){
  48.     return document.getElementById(s);
  49. }
  50. function event(obj,name,handler){
  51.     if (obj.attachEvent){
  52.         obj.attachEvent("on"+name,handler);
  53.     }else{
  54.         obj.addEventListener(name,handler);
  55.     }
  56. }
html код:
Copy Source | Copy HTML
  1. <p id="tst1" contenteditable="true">Пример на событие keypress</p>
  2. <p id="dbg1"></p>
  3. <p id="tst2" contenteditable="true">Пример на событие keydown</p>
  4. <p id="dbg2"></p>
  5. <p>Пример - Введите число 1: <input type="text" id="num1"/></p>