воскресенье, 4 мая 2014 г.

Rails file_column изменить имя загруженного файла

How to rename a file with file_column plugin.

Ruby on Rails

Собственно, понадобилось… Время от времени изменять имя файла, загруженного с помощью file_column. Например так: stylesheet.cssstylesheet_1399194798.css.

Вот, что получилось.

Читать дальше »

вторник, 15 декабря 2009 г.

Google Wave подсветка кода

Google Wave logo

Многие программисты уже используют Google Wave как средство командной разработки. В этом отношении, кстати, весьма полезный инструмент. Можно поделиться фрагментом сырого кода, прокомментировать или исправить проблемный код, предложить свой вариант. Рано или поздно каждый разработчик задает вопрос: «Как подключить подсветку синтаксиса в Google Wave»? Не так давно я нашел простое и удобное решение.


Читать дальше »

среда, 11 ноября 2009 г.

Мини-сборка Ubuntu для Ruby on Rails

Rails TurnKey Linux

Проект TurnKey Linux представил подборку из 40 готовых минималистичных сборок Ubuntu 8.04.3 LTS для быстрого развертывания в Cloud-окружениях Amazon EC2 или для использования в качестве гостевых ОС в системах виртуализации VMware, Xen HVM, KVM или VirtualBox.


И в частности, – полностью работоспособную сборку с Ruby on Rails. Которая помимо стандартного ядра TurnKey, включает в себя следующее:


  • Поддержку SSL
  • Модули Webmin для конфигурирования Apache2 и MySQL
  • Конфигурацию Ruby on Rails
    • Развертывание посредством Phusion Passenger для Apache (mod_rails)
    • Предустановленный пример Rails приложения, расположенный в /var/www/railsapp
    • Установка баз данных MySQL для production, development и testing
  • RubyGems менеджер пакетов
    • APT и RubyGems обе являются системами управления пакетами и могут конфликтовать
    • Мы рекомендуем использовать RubyGems для управления Rails компонентами (называются gems), и APT для всего остального
    • Необходимые build-essentials пакеты включены для помощи в создании gems.

Сборка представлена в виде VM (221MB) и ISO-образа (195MB).


Узнал на OpenNews.


Читать дальше »

среда, 4 ноября 2009 г.

CSS: замена таблицам

CSS document icon

Есть хорошая рекомендация, а точнее – правило: Таблицы в HTML документе использовать только по прямому назначению. А именно – для отображения табличной информации. «Трудо-часы – трудо-доллары», «дебет-кредит-итого» и подобных структурированных данных. Все остальное, как говорится, – «от лукаваго».


Недавно наш новый командный tech writer начал доводить до ума проектный FAQ и попросил помочь ему избавиться от таблиц в разметке документа. Раз уж мы пытаемся уйти от «табличной верстки» в сторону «верстки дивной», то хорошо бы чтоб новые таблицы не появлялись там, где их не ждут. За несколько минут набросал ему HTML+CSS код для двухколоночного параграфа.


Изображение и текст в 2 колонкиДвухколоночный параграф

Код HTML: табличная верстка (было)

<table width="100%" border="0"><tbody>
<tr>
<td valign="top" width="50%">
<strong>Method 1 - By logging into your ***** account</strong>:
<ol><li>Go to My Account > My Account Page. This displays the Profile page.</li>
<li>In the Profile page, locate the My Account section.</li>
<li>Click on the Edit button. Enter the new Password as requested in the correct places.</li>
<li>Click on the Save button.</li></ol>
</td>
<td width="50%">
<img src="/images/change-password.gif" alt="change your password" height="202" width="212" hspace="12" align="right" border="0">
</td>
</tr></tbody></table>


Код HTML+CSS: дивная верстка (стало)

<div style="width:50%; float:left;">
<strong>Method 1 - By logging into your ***** account</strong>:
<ol><li>Go to My Account > My Account Page. This displays the Profile page.</li>
<li>In the Profile page, locate the My Account section.</li>
<li>Click on the Edit button. Enter the new Password as requested in the correct places.</li>
<li>Click on the Save button.</li></ol>
</div>
<img style="float:right; margin:0 0 0 12px; border-width:0;" src="/images/change-password.gif" alt="change your password" height="202" width="212" />
<div style="clear:both;"></div>


Разумеется, впоследствии я выделил стили для элементов в классы CSS и отправил писателю шаблонный HTML для использования.


Надеюсь, это окажется полезным. Успехов!


Читать дальше »

четверг, 22 октября 2009 г.

Игнорируя SVN ignore

SVN – SubversionИзначально так сложилось, что для контроля версий я использовал Tortoise SVN. Но после аврального переезда на новую систему пришлось подыскивать новый SVN клиент, поскольку работа с консоли меня никак не прельщала. Выбор пал на RapidSVN. Привыкший к удобству «Тортойза», я не ожидал подвоха... и первый же коммит (svn commit) подарил много радостей всей команде. В итоге я задался вопросом как заставить SVN игнорировать выбранные файлы?


Команды задания свойств SVN


Команда svn propset
svn propset свойство маска_файлов директория

Например, находясь в директории конфигурации проекта trunk/config:
svn propset svn:ignore development.* environments


Рекомендуется по возможности использовать другую команду svn propedit
svn propedit svn:ignore environments

В открывшемся редакторе свойств, можно задать несколько масок (каждая с новой строки):
development.*
test.rb


Для удаления свойств со всей директории используют команду svn propdel
svn propdel svn:ignore environments


Проделав необходимые манипуляции и жмакнув кнопку “Commit” с отвисшей челюстью проводил отправленный в базу «проигнорированный» development.rb

Йопт! В чем дело? Почему она игнорирует мое игнорирование?!! :)

Она игнорирует


Минут пять покурив SVN Book, я нашел причину. Оказывается, поддержка масок пропуска файлов действует только на unversioned файлы.


На данный момент остановился на варианте коммита отдельных директорий:
svn commit app public -m "fixes ticket #1234"


Демотиватор дня


На тему другой системы контроля версийGit


Git trunk – Демотиватор
Без перевода :)


Бонус для RSS подписчиковSVN wallpaper


SVN wallpaper
SVN обоина для рабочего стола (1600x1000)


Читать дальше »

понедельник, 5 октября 2009 г.

Топ 10 Rails плагинов и gem-ов

Ruby Gems

Практически каждый разработчик имеет собственные предпочтения в выборе сторонних компонентов и библиотек, используемых в приложении. Не так давно мне попался такой топ-лист плагинов и gem-ов для разработки на Ruby on Rails.


10 “must-have” Rails плагинов и gem-ов


  • Inherited Resources: устраняет большую часть шаблонного кода в контроллерах;

  • Formtastic: избавляет от лишней головной боли при создании разметки HTML форм. (Используемые вместе, Inherited Resources и Formtastic составляют хорошую альтернативу scaffold-фреймворкам вроде Streamlined и ActiveScaffold);

  • CapGun: предоставляет простые уведомления о развертывании (deploy);

  • Faker: помогает создавать фиктивные данные. Может использоваться как для тестирования (test) приложений, так и для создания демонстрационных данных в режиме разработки (development) и постановки (staging);

  • Clearance: многофункциональная регистрация и аутентификация;

  • Safe ERB: помогает убедиться, что приложение неуязвимо для XSS-атак;

  • RedHill on Rails Core: в основном, используется для декларирования “foreign key”-отношений в схеме БД;

  • RPM Rails Performance Management: замечательно для обнаружения и диагностики проблем производительности;

  • will_paginate: самый хороший и самый простой из плагинов постраничной разбивки (pagination);

  • hoptoad: великолепные «кастомер-френдли» уведомления об исключениях (exceptions) возникающих в приложении.

Существует множество плагинов, которые мы используем для специальных нужд, как то генерирование PDF или обработка вложений (attachment). Однако это список наших избранных плагинов, которые мы используем практически в каждом проекте.

Это был список лучших 10 Rails плагинов и gem-ов от Глена Вандербурга (Glenn Vanderburg), опубликованный им в своем блоге: 10 Must-Have Rails Plugins and Gems (2009 Edition).


Читать дальше »

вторник, 8 сентября 2009 г.

Стоп! Хамматайм!

Stop. Hammertime!

«Если кобыла сдохла, слезь с нее!» Именно так я вчера и поступил. А что еще делать? Прошел месяц (!!!), а «отмашки» все нет, равно как нет и хоть каких-нибудь комментариев или прогнозов.

В общем, «откладывать дальше уже некуда» © Goblin. Тем более коммитить (Commit – предавать) тему надо по-любому, пусть и в «альфе». Во-первых: уже «уплочено», во-вторых ее все равно на рассмотрение предоставить надо. А там уже решать, поскольку одной сменой лэйаута (layout – макет) дело не обойдется. Одних аяксовых (AJAX«асинхронный JavaScript и XML») функциональностей нужно будет переделать на полтора ведра с ковшиком.

Предупредил народ и вечером таки закоммитил изменения в SVN репозиторий. Нехрена им “unversioned” болтаться. Кстати, RapidSVN отнюдь не порадовал.

Вот так. Буду искать новый проект.

К сожалению, это не единственная «дохлая кобыла», с которой мне придется таки слезть.

Читать дальше »

суббота, 11 июля 2009 г.

Новое в гад-же-то-Blogger-строении

Важная новость от Blogger Buzz! Команда Blogger запустила несколько расширений для Gadget API, которые позволят разработчикам ваять более релевантные, симпатичные и улыбчивые гаджеты для Blogger.


Каждый блог на данной платформе это контейнер гаджетов. Более того, благодаря Friend Connect, это еще и OpenSocial гаджет-контейнер. Это означает, что как разработчик ты можешь использовать social APIs для создания более удобных инструментов для блоггеров и их аудитории.


В добавок к использованию социальных инструментов, гаджеты теперь могут получать доступ к фидам (feeds) сообщений и комментариев блога через новые JSON APIs. Например, используя эти данные, ты можешь создать гаджет карты, на которой будешь отмечать географию сообщений, или гаджет популярных сообщений.


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


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

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


Читать дальше »

пятница, 10 июля 2009 г.

Объектно-ориентированное программирование

В продолжение программы по обновлению и дополнению «теоретического багажа» в данном посте разбираю парадигму ООП. Эта статья в первую очередь, – шпаргалка для себя. Поэтому постараюсь избежать излишней детализации. Дополнительные детали, на мой взгляд, «жизненно необходимы» лысеющим теоретикам, да судорожно содрогающимся студентам на экзаменах. ..Сдаваемых лысеющим теоретикам.


Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.


Класс — это тип, описывающий устройство объектов. Понятие «класс» подразумевает некоторое поведение и способ представления. Понятие «объект» подразумевает нечто, что обладает определённым поведением и способом представления. Говорят, что объект — это экземпляр класса. Класс можно сравнить с чертежом, согласно которому создаются объекты. Обычно классы разрабатывают таким образом, чтобы их объекты соответствовали объектам предметной области.


Класс является моделью ещё не существующей сущности, объекта.


Объект — сущность, появляющаяся при создании экземпляра класса.


Основные понятия


Абстракция данных
Объекты представляют собою упрощенное, идеализированное описание реальных сущностей предметной области. Если соответствующие модели адекватны решаемой задаче, то работать с ними оказывается намного удобнее, чем с низкоуровневым описанием всех возможных свойств и реакций объекта.

Инкапсуляция
Инкапсуляция — это принцип, согласно которому любой класс должен рассматриваться как «чёрный ящик» — пользователь класса должен видеть и использовать только интерфейсную часть класса (т. е. список декларируемых свойств и методов класса) и не вникать в его внутреннюю реализацию. Поэтому данные принято инкапсулировать в классе таким образом, чтобы доступ к ним по чтению или записи осуществлялся не напрямую, а с помощью методов. Принцип инкапсуляции (теоретически) позволяет минимизировать число связей между классами и, соответственно, упростить независимую реализацию и модификацию классов.

Сокрытие данных
Сокрытие данных — неотделимая часть ООП, управляющая областями видимости. Является логическим продолжением инкапсуляции. Целью сокрытия является невозможность для пользователя узнать или испортить внутреннее состояние объекта.

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

Полиморфизм
Полиморфизмом называют явление, при котором функции (методу) с одним и тем же именем соответствует разный программный код (полиморфный код) в зависимости от того, объект какого класса используется при вызове данного метода. Полиморфизм обеспечивается тем, что в классе-потомке изменяют реализацию метода класса-предка с обязательным сохранением сигнатуры метода. Это обеспечивает сохранение неизменным интерфейса класса-предка и позволяет осуществить связывание имени метода в коде с разными классами — из объекта какого класса осуществляется вызов, из того класса и берётся метод с данным именем. Такой механизм называется динамическим (или поздним) связыванием — в отличие от статического (раннего) связывания, осуществляемого на этапе компиляции.


Абстрактные методы и классы
При построении сложной иерархии, для обеспечения полиморфизма программисты часто вынуждены вводить методы в классы верхнего уровня, даже если эти методы ещё не определены для этой специфической абстракции. Здесь можно было бы оставить пустые методы, но многие ОО языки предлагают такой специфический механизм, как определение абстрактных методов, то есть методов без реализации. Классы, имеющие хотя бы один абстрактный метод, часто называется абстрактным классом.

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

Определение ООП


Объектно-ориентированный подход заключается в следующем наборе основных принципов:

  1. Всё является объектом.
  2. Вычисления осуществляются путём взаимодействия (обмена данными) между объектами, при котором один объект требует, чтобы другой объект выполнил некоторое действие. Объекты взаимодействуют, посылая и получая сообщения. Сообщение — это запрос на выполнение действия, дополненный набором аргументов, которые могут понадобиться при выполнении действия.
  3. Каждый объект имеет независимую память, которая состоит из других объектов.
  4. Каждый объект является представителем (экземпляром) класса, который выражает общие свойства объектов.
  5. В классе задаётся поведение (функциональность) объекта. Тем самым все объекты, которые являются экземплярами одного класса, могут выполнять одни и те же действия.
  6. Классы организованы в единую древовидную структуру с общим корнем, называемую иерархией наследования. Память и поведение, связанное с экземплярами определённого класса, автоматически доступны любому классу, расположенному ниже в иерархическом дереве.


Особенности реализации


Как уже говорилось выше, в современных объектно-ориентированных языках программирования каждый объект является значением, относящимся к определённому классу). Класс представляет собой объявленный программистом составной тип данных, имеющий в составе:

Поля данных
Параметры объекта (конечно, не все, а только необходимые в программе), задающие его состояние (свойства объекта предметной области). Иногда поля данных объекта называют свойствами объекта, из-за чего возможна путаница. Физически поля представляют собой значения (переменные, константы), объявленные как принадлежащие классу.

Методы
Процедуры и функции, связанные с классом. Они определяют действия, которые можно выполнять над объектом такого типа, и которые сам объект может выполнять.


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


Инкапсуляция обеспечивается следующими средствами:

Контроль доступа 
Поскольку методы класса могут быть как чисто внутренними, обеспечивающими логику функционирования объекта, так и внешними, с помощью которых взаимодействуют объекты, необходимо обеспечить скрытость первых при доступности извне вторых. Для этого в языки вводятся специальные синтаксические конструкции, явно задающие область видимости каждого члена класса. Традиционно это модификаторы:
  • public – открытые члены класса;
  • protected – члены класса, доступные только из классов-потомков;
  • private – скрытые, доступные только внутри класса.

Конкретная номенклатура модификаторов и их точный смысл различаются в разных языках.

Методы доступа 
Поля класса, в общем случае, не должны быть доступны извне, поскольку такой доступ позволил бы произвольным образом менять внутреннее состояние объектов. Поэтому поля обычно объявляются скрытыми (либо язык в принципе не позволяет обращаться к полям класса извне), а для доступа к находящимся в полях данным используются специальные методы, называемые методами доступа. Такие методы либо возвращают значение того или иного поля, либо производят запись в это поле нового значения. При записи метод доступа может проконтролировать допустимость записываемого значения и, при необходимости, произвести другие манипуляции с данными объекта, чтобы они остались корректными (внутренне согласованными). Методы доступа называют ещё аксессорами (от англ. access — доступ), а по отдельности — геттерами (англ. get — чтение) и сеттерами (англ. set — запись).

Свойства объекта
Псевдополя, доступные для чтения и/или записи. Свойства внешне выглядят как поля и используются аналогично доступным полям (с некоторыми исключениями), однако фактически при обращении к ним происходит вызов методов доступа. Таким образом, свойства можно рассматривать как «умные» поля данных, сопровождающие доступ к внутренним данным объекта какими-либо дополнительными действиями (например, когда изменение координаты объекта сопровождается его перерисовкой на новом месте). Свойства никаких новых возможностей не добавляют, а лишь скрывают вызов методов доступа. Конкретная языковая реализация свойств может быть разной.


Почитать по теме:


Читать дальше »

среда, 8 июля 2009 г.

DHTML Tab Connection Panel

Результат выполнения еще одного «тестового задания». ..И в который раз затрудняюсь с выбором кавычек. Выкладываю коды здесь, вдруг да когда пригодится. Использовался фреймворк Prototype. Сам текст задания, возможно, выложу позже.


Скриншот



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


  • при добавления большого количества вкладок отсутствуют боковые границы рядов;
  • на странице main.html не отслеживается обновление (через F5) страницы child.html
  • в IE 6 было замечено вертикальное смещение правой границы динамически созданных вкладок;
  • на странице child.html не отслеживается наличие открывающего документа document.opener (main.html);
  • скрипт бесполезно проверять инструментами вроде IETester. Как правило, эти инструменты не отслеживают document.opener;
  • отключена возможность удаления активной вкладки с помощью CSS, отсутствует контроль этого процесса средствами javascript.

Изображения




Изображения получены из открытых источников


main.html


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html dir='ltr' xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Main Window</title>
<link type='text/css' rel='stylesheet' href='public/stylesheets/general.css' />
<script type="text/javascript" src="public/javascripts/prototype.js"></script>
<script type="text/javascript" src="public/javascripts/main.js"></script>
</head>
<body>
<p><input id="btnOpen" type="button" value="Изменить..." onclick="return btnOpen_onclick();" /></p>
<div id="connections"></div>
</body>
</html>


child.html


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>DHTML Tab Connection Panel</title>
<script type="text/javascript" src="public/javascripts/prototype.js"></script>
<link type='text/css' rel='stylesheet' href='public/stylesheets/general.css' />
<script type="text/javascript" src="public/javascripts/child.js"></script>
</head>
<body>
<p id="navBar">
<input id="btnAdd" type="button" tabindex="1" value="Добавить" onclick="tabView.addControl(); return false;" />
<a id="aTabPrev" href="#" tabindex="2" onclick="tabView.tabPrev(); return false;">←</a>
<kbd>Ctrl</kbd>
<a id="aTabNext" href="#" tabindex="3" onclick="tabView.tabNext(); return false;">→</a>
</p>

<div id="tabView1" class="tabView">
<div id="tabPanel" class="tabPane">
<div id="tabViewTab_0" class="tabActive" style="left: 0px;" onclick="tabView.tabSelect(0); return false;">
<div class="icon"></div>
<span tabindex="4"><span id="tabText_0" class="tabText">Guest</span>
<a href="#" onclick="tabView.destroy(0); return false;"><img class="close" width="8" height="8" src="public/images/icon_delete.gif" /></a>
</span>
<img class="tab_right" src="public/images/tab_right.gif" />
</div>
</div>
<div class="clear"></div>

<div id="aFormCont" class="aTab">
<form id="aForm" action="">
<input id="index" type="hidden" />
<div>Логин</div>
<div><input id="login" type="text" onchange="tabView.onChange();" /></div>
<div>Пароль</div>
<div><input id="password" type="password" onchange="tabView.onChange();" /></div>
<div><input id="remember_login" type="checkbox" onchange="tabView.onChange();" /><label for="remember_login">Запомнить логин</label></div>
<div><input id="remember_password" type="checkbox" onchange="tabView.onChange();" /><label for="remember_password">Запомнить пароль</label></div>
<div><input id="auto_login" type="checkbox" onchange="tabView.onChange();" /><label for="auto_login">Входить автоматически</label></div>
<p style="text-align:right;">
<input id="button_submit" type="button" value="Подключить" onclick="tabView.onChangeStatus();" />
</p>
</form>
</div>
</div>
</body>
</html>

general.css


/* General */
body{
margin:10px;
font-size:0.9em;
}

/* Main */
#connections p {
border:1px solid #919B9C;
padding: 0 0.2em;
width: 500px;
font-family: Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif;
}

/* Child */
a{
color:#F00;
}
.tabView {
width:500px;
height:400px;
}
.tabPane{
height:21px; /* Height of tabs */
border-bottom:1px solid #919b9c;
}
.aTab{
border-left:1px solid #919b9c;
border-right:1px solid #919b9c;
border-bottom:1px solid #919b9c;
font-family: Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif;
padding:5px;

}
.tabPane div{
float:left;
height:100%; /* Height of tabs */
padding-left:3px;
vertical-align:middle;
background-repeat:no-repeat;
background-position:bottom left;
cursor:pointer;
position:relative;
bottom:-1px;
margin-left:0px;
margin-right:0px;
}
.tabPane .tabActive{
background-image:url('../images/tab_left_active.gif');
margin-left:0px;
margin-right:0px;
z-index:10;
}
.tabPane .tabInactive{
background-image:url('../images/tab_left_inactive.gif');
margin-left:0px;
margin-right:0px;
z-index:10;
}
.tabPane .inactiveTabOver{
background-image:url('../images/tab_left_over.gif');
margin-left:0px;
margin-right:0px;
}
.tabPane span{
font-family:arial;
vertical-align:top;
font-size:11px;
padding-left:3px;
padding-right:3px;
line-height:21px;
float:left;
}
.tabPane .tabActive span{
padding-bottom:1px;
line-height:20px;
position: relative;
padding-left: 4px;
padding-right: 12px;
}
.tabPane .tabInactive span{
padding-bottom:1px;
line-height:20px;
position: relative;
padding-left: 3px;
padding-right:10px;
}
.tabPane img{
float:left;
}
.tabPane .tabActive img.tab_right {
background-image:url(../images/tab_right_active.gif);
}
.tabPane .tabInactive img.tab_right {
background-image:url(../images/tab_right_inactive.gif);
}
.tabActive a {
display:none;
}
.icon {
background-image:url(../images/globe_icon.gif);
background-repeat:no-repeat;
background-position:bottom left;
width:15px;
}
img.close {
position: absolute;
top: 6px;
right: 0;
border:0;
}
a img.close {
opacity:.01;
filter: alpha(opacity=1); /* IE */
-moz-opacity: 0.01; /* Firefox */
}
a:hover img.close {
opacity:1;
filter: alpha(opacity=100); /* IE */
-moz-opacity: 1; /* Firefox */
}
.tabText{
min-width:30px;
display:inline-block;
}
.clear {
clear: both;
}

/* Navigation Bar */
#navBar {
width:500px;
text-align:right;
}
#navBar input {
float:left;
}
#navBar a {
text-decoration:none;
}
#navBar kbd {
font-size:larger;
}

/* Form elements */
input[type="text"], input[type="password"] {border:1px solid #919b9c;}
/* for IE */
input { border:expression((this.type=='text')||(this.type=='password') ? '1px solid #919b9c' : ''); }

main.js


// <!CDATA[
var pPrefix = "connection_";
var prefixLogin = "login_";

document.addConnection = addConnection;
document.updateConnection = updateConnection;
document.destroyConnection = destroyConnection;
document.updateConnectionStatus = updateConnectionStatus;

/* Open Child document */
function btnOpen_onclick(){
open("child.html", "Child");
}

/* Helpers */
bool2human = function(value){
return (value == true) ? "да" : "нет";
}

humanStatus = function(value){
return (value == true) ? "Подключен" : "Отключен";
}

/* Dynamic template for #connections P.innerHTML */
getTemplate = function(attr){
return 'Логин: <span class="login">'+ attr.login +'</span><br />'+
'Пароль: <span class="password">'+ attr.password +'</span><br />'+
'Запомнить: логин (<span class="remember_login">'+ bool2human(attr.remember_login) +'</span>),'+
' пароль (<span class="remember_password">'+ bool2human(attr.remember_password) +'</span>)<br />'+
'Входить автоматически (<span class="auto_login">'+ bool2human(attr.auto_login) +'</span>)<br />'+
'<span class="status">'+ humanStatus(attr.status) +'</span>';
}

/* functions called by Child document */
function addConnection(attr){
try {
var p1 = document.createElement("p");
p1.id = pPrefix + attr.index;
var template = getTemplate(attr);
p1.innerHTML = template;
$("connections").appendChild(p1);
}
catch (e) {
alert(e.description);
}
}

function updateConnection(attr){
try {
var p1 = $(pPrefix + attr.index);
var template = getTemplate(attr);
p1.innerHTML = template;
}
catch (e) {
alert(e.description);
}
}

function destroyConnection(index){
var p1 = $(pPrefix + index);
Element.remove(p1);
}

function updateConnectionStatus(index, status){
var cssExpr = $$("#"+ pPrefix + index +" .status");
cssExpr[0].innerHTML = humanStatus(status);
}
// ]]>

child.js


document.onkeydown = navigateTabs;

function navigateTabs (event){
if (window.event) event = window.event;
if (event.ctrlKey){
switch (event.keyCode ? event.keyCode : event.which ? event.which : null){
case 0x25:
tabView.tabPrev();
break;
case 0x27:
tabView.tabNext();
break;
}
}
}

/* Class: Tab View */
TabView = function(){
this.tabPrefix = "tabViewTab_";
this.tabTextPrefix = "tabText_";
this.tabPanelId = "tabPanel";
this.items = new Array();
this.cnActive = "tabActive";
this.cnInactive = "tabInactive";
this.patternTabActive = "#tabView1 ." + this.cnActive;
this.tabIndexShift = 4; //сдвиг tabindex

this.init = function(){
this.add("Guest", "guest", true, true, false, false);
}

this.destroy = function(index){
if ( $(this.tabPrefix + index) != undefined ){
this.items[index] = null;
this.destroyConnection(index);
Element.remove(this.tabPrefix + index);
}
}

this.add = function(login, password, remember_login, remember_password, auto_login, status){
var index = this.items.length;
var Item = new Object();
Item.index = index;
Item.login = login;
Item.password = password;
Item.remember_login = remember_login;
Item.remember_password = remember_password;
Item.auto_login = auto_login;
Item.status = status;
this.items.push(Item);
this.addConnection(Item);
}

/* Functions for Document.Opener */
this.addConnection = function(obj){
opener.document.addConnection(obj);
}
this.updateConnection = function(obj){
opener.document.updateConnection(obj);
}
this.destroyConnection = function(index){
opener.document.destroyConnection(index);
}
this.updateConnectionStatus = function(index, status){
opener.document.updateConnectionStatus(index, status);
}

/* Helpers */
this.humanStatus = function(value){
return (value == true) ? "Отключить" : "Подключить";
}

this.checkbox2bool = function(value){
return (value == null) ? false : true;
}

/* DOM Functions */
this.addControl = function(){
this.add("", "", false, false, false, false);

var index = this.items.length-1;
var p1 = document.createElement("p");
var div1 = document.createElement("div");
div1.id = this.tabPrefix + index;
div1.className = this.cnInactive;//;
div1.onclick = function(){ tabView.tabSelect(index); return false; };

var div2 = document.createElement("div");
div2.className = "icon";
div1.appendChild(div2);

var span1 = document.createElement("span");
span1.setAttribute("tabindex", index*1 + this.tabIndexShift);

var span2 = document.createElement("span");
span2.id = this.tabTextPrefix + index;
span2.className = "tabText";
span1.appendChild(span2);

var a1 = document.createElement("a");
a1.href = "#";
a1.onclick = function(){ tabView.destroy(index); return false; };

var img1 = document.createElement("img");
img1.className = "close";
img1.setAttribute("width", 8);
img1.setAttribute("height", 8);
img1.setAttribute("src", "public/images/icon_delete.gif");
a1.appendChild(img1);
span1.appendChild(a1);

var img2 = document.createElement("img");
img2.className = "tab_right";
img2.setAttribute("src", "public/images/tab_right.gif");

div1.appendChild(span1);
div1.appendChild(img2);
$(this.tabPanelId).appendChild(div1);
}

this.fillForm = function(obj){
$("index").value = obj.index;
$("login").value = obj.login;
$("password").value = obj.password;
$("remember_login").checked = obj.remember_login;
$("remember_password").checked = obj.remember_password;
$("auto_login").checked = obj.auto_login;
$("button_submit").value = this.humanStatus(obj.status);
}

this.tabSelect = function(index){
//scope for each() function
cnActive = this.cnActive;
cnInactive = this.cnInactive;
if ( $(this.tabPrefix + index) != undefined ){
//off
$$(this.patternTabActive).each( function(elem){
Element.removeClassName(elem, cnActive);
Element.addClassName(elem, cnInactive);
});
//on
Element.removeClassName(this.tabPrefix + index, this.cnInactive);
Element.addClassName(this.tabPrefix + index, this.cnActive);
this.fillForm(this.items[index]);
return true;
}
return false;
}

/* onModyfy functions */
this.onChange = function(){
var index = $F("index");

var Item = this.items[index];
Item.index = index;
Item.login = $F("login");
Item.password = $F("password");
Item.remember_login = this.checkbox2bool( $F("remember_login") );
Item.remember_password = this.checkbox2bool( $F("remember_password") );
Item.auto_login = this.checkbox2bool( $F("auto_login") );

$(this.tabTextPrefix + index).innerHTML = Item.login;

this.updateConnection(Item);
}

this.onChangeStatus = function(){
var index = $F("index");
var Item = this.items[index];
Item.status = (Item.status == true) ? false : true;
this.updateConnectionStatus(Item.index, Item.status);
$("button_submit").value = this.humanStatus(Item.status);
}

/* Navigation through Tabs */
this.tabPrev = function(){
var index = $F("index");
for (var i = index*1-1; i >= this.items.first().index; i--){
if (this.tabSelect(i)){
break;
}
}
}

this.tabNext = function(){
var index = $F("index");
for (var i = index*1+1; i <= this.items.last().index; i++){
if (this.tabSelect(i)){
break;
}
}
}

this.init();
}
// End: Class TabView

// Init tabView
tabView = new TabView();
Event.observe(window, "load", function() {
tabView.fillForm(tabView.items[0]);
});

Вот как-то так :).


Creative Commons License
DHTML Tab Connection Panel by JCDen (aka Croaker) is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
Читать дальше »

пятница, 26 июня 2009 г.

Sitemap на Ruby on Rails

Пару дней назад озадачился генерированием файла sitemap.xml на Ruby on Rails для пользовательских торговых площадок. Приведу упрощенный пример без блогов, форумов и специальных страниц. В файле Sitemap будут url для:

  • индексной страницы магазина (index)
  • страницы описания (about)
  • страницы контактов (contact)
  • всех страниц товаров (item)


Контроллер (store_controller.rb)

def sitemap
@store = Store.find(params[:id])
@items = @store.items.find :all,
:conditions => "available_for_sale = 1",
:order => "updated_at DESC",
:limit => 49997
end


В соответствии с концепцией MVC, процедуру выборки товаров лучше вынести в модель.


В рекомендациях по файлам Sitemap для Google оговаривается, что файл Sitemap может содержать не более 50000 URL. За вычетом страниц индекса, описания и контактов, остается 49997 позиций для URL товаров.


Представление (sitemap.xml.builder)

xml.instruct! :xml, :version => "1.0", :encoding=>"UTF-8"
xml.urlset(:xmlns => "http://www.sitemaps.org/schemas/sitemap/0.9") {
# Index
xml.url {
xml.loc(url_for :controller => :store, :action => :index, :id => @store.id, :format => :xml, :only_path => false)
lastmod = @items.first.updated_at.strftime("%Y-%m-%d") unless @items.nil?
lastmod = lastmod || Time.now.strftime("%Y-%m-%d")
xml.lastmod(lastmod)
xml.changefreq("daily")
xml.priority(1)
}
# About
xml.url {
xml.loc(url_for :controller => :store, :action => :about, :id => @store.id, :format => :xml, :only_path => false)
xml.lastmod(@store.updated_at.strftime("%Y-%m-%d"))
xml.changefreq("monthly")
xml.priority(0.7)
}
# Contact
xml.url {
xml.loc(url_for :controller => :store, :action => :contact, :id => @store.id, :format => :xml, :only_path => false)
xml.lastmod(@store.updated_at.strftime("%Y-%m-%d"))
xml.changefreq("monthly")
xml.priority(0.5)
}
# Items
@items.each do |item|
xml.url {
xml.loc(url_for :controller => 'store', :action => 'item', :id => item.id, :format => :xml, :only_path => false)
xml.lastmod(item.updated_at.strftime("%Y-%m-%d"))
xml.changefreq("weekly")
xml.priority(0.9)
}
end
}


Страницы пользовательских магазинов у меня выводятся в формате XML с (XSL трансформацией) и при генерации url я использую параметр :format => :xml. В стандартном выводе этот параметр не нужен.


Для каждого URL адреса тег <loc> является обязательным; остальные теги являются дополнительными.

  • loc – адрес страницы;
  • lastmod – дата последнего обновления;
  • changefreq – частота изменения страницы;
  • priority – приоритет.


Главная страница магазина (index) и страницы товаров (item) являются наиболее важными страницами, поэтому имеют более высокий приоритет. Страница описания и страница контактов, имхо уже «на интерес». Следовательно, имеют приоритет более низкий.


Индексная страница обновляется довольно часто за счет добавления новых товаров и выбывания товаров из листинга. Поэтому обновляется ежедневно (daily).


Страницы товаров тоже обновляются довольно часто. За счет добавления отзывов, комментариев клиентов. Но эта информация не особо важна. Спокойно могу задать им еженедельное (weekly) обновление.


Информация о магазине и контактах, как правило, изменяется крайне редко, и обновлять ее стоит не чаще, чем раз в месяц (monthly).


Успехов на рельсах! © dotrb.blogspot.com


Читать дальше »

вторник, 23 июня 2009 г.

Google Chrome: баг с ифреймом

google chrome

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


На странице два элемента <iframe>. В одном чат, в другом ротатор баннеров.

<!-- В этом фрейме работает флешевый чат -->
<iframe id="frame_chat" src="/userplane/chat" frameborder="no"></iframe>
<!-- В этом фрейме сторонний скрипт баннеро-ротатор -->
<iframe id="frame_ads" src="http://third_part_banner_rotator" frameborder="no"></iframe>


Скрипт приводить не буду. Действует так: через каждые 30 секунд создает frameset с фреймами. Во фреймах различные составные рекламные баннеры.


Все браузеры (даже IE!), как и задумано, перезагружают только содержимое ифрейма "frame_ads", а Chrome – страницу целиком.


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

<iframe id="frame_ads" src="http://third_part_banner_rotator" frameborder="no" />


И всё заработало :). Сыроват еще «Хром», сыроват...


Читать дальше »

четверг, 5 февраля 2009 г.

Черно-белое фото с помощью PHP

Marilyn MonroeМногим людям нравится черно-белое фото. Такое фото создает некий эффект «классики», «старомодности», зрелости. Vintage -- одним словом. Вы не знали? -- «раньше небо было голубее, солнце желтее...»*, а PHP и в помине не было.

*Это прекрасно видно на черно-белых снимках ;)
И тем не менее пример из недавней практики -- как сделать цветное изображение черно-белым на PHP с помощью библиотеки GD.
function grayscale($filename){
//Получаем размеры изображения
$img_size = GetImageSize($filename);
$width = $img_size[0];
$height = $img_size[1];
//Создаем новое изображение с такмими же размерами
$img = imageCreate($width,$height);
//Задаем новому изображению палитру "оттенки серого" (grayscale)
for ($c = 0; $c < 256; $c++) {
ImageColorAllocate($img, $c,$c,$c);
}
//Содаем изображение из файла Jpeg
$img2 = ImageCreateFromJpeg($filename);
//Объединяем два изображения
ImageCopyMerge($img,$img2,0,0,0,0, $width, $height, 100);
//Сохраняем полученное изображение
imagejpeg($img, $filename);
//Освобождаем память, занятую изображением
imagedestroy($img);
}

Этот метод сохраняет изображения в черно-белой гамме. Например, при загрузке файлов на сервер.

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

function show_grayscale($filename){
$img_size = GetImageSize($filename);
$width = $img_size[0];
$height = $img_size[1];
$img = imageCreate($width,$height);
for ($c = 0; $c < 256; $c++) {
ImageColorAllocate($img, $c,$c,$c);
}
$img2 = ImageCreateFromJpeg($filename);
ImageCopyMerge($img,$img2,0,0,0,0, $width, $height, 100);
//Отдаем полученное изображение браузеру
header("Content-type: image/jpeg");
imagejpeg($img);

imagedestroy($img);
}

Pro et Contra

..или «За и Против» обоих методов.
В первом примере:
  • (+) ваше приложение нагружается всего один раз -- когда вы сохраняете изображение;
  • (-) на сервере у вас хранятся черно-белые изображения. Цветных вы из них уже не сделаете.

Во втором примере:
  • (+) на сервере у вас хранятся цветные изображения. И вы всегда можете выбрать как их отобразить;
  • (-) ваше приложение нагружается каждый раз -- когда вы отдаете черно-белое изображение браузеру.

Читать дальше »

четверг, 29 января 2009 г.

Сортировка по цвету на Ruby

Не в первый раз уже сталкиваюсь с задачей сортировки по цвету. То есть как произвольный набор цветов в 16-ричном формате сортировать по насыщенности цвета. Сегодня я кажется решил эту задачу.

Сортировка по цвету
Итак, произвольный набор цветов в виде хеша (Hash). Кому понравится -- берите не стесняйтесь.

Код ruby


simple_colors = {
:aliceblue => "f0f8ff",
:antiquewhite => "faebd7",
:aqua => "00ffff",
:aquamarine => "7fffd4",
:azure => "f0ffff",
:beige => "f5f5dc",
:bisque => "ffe4c4",
:black => "000000",
:blanchedalmond => "ffebcd",
:blue => "0000ff",
:blueviolet => "8a2be2",
:brown => "a52a2a",
:burlywood => "deb887",
:cadetblue => "5f9ea0",
:chartreuse => "7fff00",
:chocolate => "d2691e",
:coral => "ff7f50",
:cornflowerblue => "6495ed",
:cornsilk => "fff8dc",
:crimson => "dc143c",
:cyan => "00ffff",
:darkblue => "00008b",
:darkcyan => "008b8b",
:darkgoldenrod => "b8860b",
:darkgray => "a9a9a9",
:darkgreen => "006400",
:darkkhaki => "bdb76b",
:darkmagenta => "8b008b",
:darkolivegreen => "556b2f",
:darkorange => "ff8c00",
:darkorchid => "9932cc",
:darkred => "8b0000",
:darksalmon => "e9967a",
:darkseagreen => "8fbc8f",
:darkslateblue => "483d8b",
:darkslategray => "2f4f4f",
:darkturquoise => "00ced1",
:darkviolet => "9400d3",
:deeppink => "ff1493",
:deepskyblue => "00bfff",
:dimgray => "696969",
:dodgerblue => "1e90ff",
:feldspar => "d19275",
:firebrick => "b22222",
:floralwhite => "fffaf0",
:forestgreen => "228b22",
:fuchsia => "ff00ff",
:gainsboro => "dcdcdc",
:ghostwhite => "f8f8ff",
:gold => "ffd700",
:goldenrod => "daa520",
:gray => "808080",
:green => "008000",
:greenyellow => "adff2f",
:honeydew => "f0fff0",
:hotpink => "ff69b4",
:indianred => "cd5c5c",
:indigo => "4b0082",
:ivory => "fffff0",
:khaki => "f0e68c",
:lavender => "e6e6fa",
:lavenderblush => "fff0f5",
:lawngreen => "7cfc00",
:lemonchiffon => "fffacd",
:lightblue => "add8e6",
:lightcoral => "f08080",
:lightcyan => "e0ffff",
:lightgoldenrodyellow => "fafad2",
:lightgrey => "d3d3d3",
:lightgreen => "90ee90",
:lightpink => "ffb6c1",
:lightsalmon => "ffa07a",
:lightseagreen => "20b2aa",
:lightskyblue => "87cefa",
:lightslateblue => "8470ff",
:lightslategray => "778899",
:lightsteelblue => "b0c4de",
:lightyellow => "ffffe0",
:lime => "00ff00",
:limegreen => "32cd32",
:linen => "faf0e6",
:magenta => "ff00ff",
:maroon => "800000",
:mediumaquamarine => "66cdaa",
:mediumblue => "0000cd",
:mediumorchid => "ba55d3",
:mediumpurple => "9370d8",
:mediumseagreen => "3cb371",
:mediumslateblue => "7b68ee",
:mediumspringgreen => "00fa9a",
:mediumturquoise => "48d1cc",
:mediumvioletred => "c71585",
:midnightblue => "191970",
:mintcream => "f5fffa",
:mistyrose => "ffe4e1",
:moccasin => "ffe4b5",
:navajowhite => "ffdead",
:navy => "000080",
:oldlace => "fdf5e6",
:olive => "808000",
:olivedrab => "6b8e23",
:orange => "ffa500",
:orangered => "ff4500",
:orchid => "da70d6",
:palegoldenrod => "eee8aa",
:palegreen => "98fb98",
:paleturquoise => "afeeee",
:palevioletred => "d87093",
:papayawhip => "ffefd5",
:peachpuff => "ffdab9",
:peru => "cd853f",
:pink => "ffc0cb",
:plum => "dda0dd",
:powderblue => "b0e0e6",
:purple => "800080",
:red => "ff0000",
:rosybrown => "bc8f8f",
:royalblue => "4169e1",
:saddlebrown => "8b4513",
:salmon => "fa8072",
:sandybrown => "f4a460",
:seagreen => "2e8b57",
:seashell => "fff5ee",
:sienna => "a0522d",
:silver => "c0c0c0",
:skyblue => "87ceeb",
:slateblue => "6a5acd",
:slategray => "708090",
:snow => "fffafa",
:springgreen => "00ff7f",
:steelblue => "4682b4",
:tan => "d2b48c",
:teal => "008080",
:thistle => "d8bfd8",
:tomato => "ff6347",
:turquoise => "40e0d0",
:violet => "ee82ee",
:violetred => "d02090",
:wheat => "f5deb3",
:white => "ffffff",
:whitesmoke => "f5f5f5",
:yellow => "ffff00",
:yellowgreen => "9acd32"
}

А вот и метод сортировки:

Код ruby: сортировка по насыщенности


ordered_colors = simple_colors.sort{|a,b| ( a[1][0,2].to_i(16)+a[1][2,2].to_i(16)+a[1][4,2].to_i(16) )<=>(b[1][0,2].to_i(16)+b[1][2,2].to_i(16)+b[1][4,2].to_i(16))}

Внимание: метод sort класса Hash возвращает массив (Array).

Пример: hsh.sort -> anArray


h = { "a" => 20, "b" => 30, "c" => 10  }  
h.sort » [["a", 20], ["b", 30], ["c", 10]]

И вывод отсортированного набора цветов в виде (.rhtml):
<style type="text/css">
.colorPick {
width:16px; height:16px; float:left;
border:2px solid #FFF;
margin:1px;
}
</style>
<!-- ... -->
<%
ordered_colors.each do |color|
name = color[0]
value = color[1]
%>
<div class="colorPick"
style="background-color: #<%= value %>"
title="<%= name %>">
</div>
<% end %>


В итоге у вас должно получиться нечто похожее, как на картинке в начале поста.

© dotrb.blogspot.com 2009
Читать дальше »

среда, 28 января 2009 г.

Radio group value с помощью Javascript

Недавно начал разрабатывать инструментарий для управления стилями шаблона пользователя, основанными на использовании переменных CSS. Задумал нечто похожее на реализацию в Blogger инструмента «Шрифты и Цвета».

В процессе написания javascript класса столкнулся с проблемой: как получить значение группы радио переключателей (radio group)?

Дело в том, что каждый переключатель имеет собственный id и собственное значение value. В группу же они объединяются по атрибуту name (name у них одинаковый).

<input type="radio" name="font-face" id="font-face-georgia" value="Georgia, Times, serif" />
<label for="font-face-georgia" style="font-family: Georgia;" class="font-label">Georgia</label>

<input type="radio" name="font-face" id="font-face-arial" value="Arial, sans-serif" />
<label class="font-label" style="font-family: Arial;" for="font-face-arial">Arial</label>

<input type="radio" name="font-face" id="font-face-courier" value="Courier, monospace" />
<label for="font-face-courier" style="font-family: Courier;" class="font-label">Courier</label>

При обработке/отправке формы (form) отправляется value переключателя, у которого установлен атрибут checked или checked="checked".

Radio group value на «чистом» javascript


var radioGrp = document['forms']['form_name_or_id']['radio_grp_name'];
for(i=0; i < radioGrp.length; i++){
if (radioGrp[i].checked == true){
var radioValue = radioGrp[i].value;
}
}

В данном случае вы должны знать name или id формы и name группы переключателей.

Radio group value с использованием Prototype javascript


Внимание: в данном примере используется javascript-фреймворк Prototype.
function $RF(el, radioGroup) {
if($(el).type && $(el).type.toLowerCase() == 'radio') {
var radioGroup = $(el).name;
var el = $(el).form;
} else if ($(el).tagName.toLowerCase() != 'form') {
return false;
}

var checked = $(el).getInputs('radio', radioGroup).find(
function(re) {return re.checked;}
);
return (checked) ? $F(checked) : null;
}

В этом случае вы получаете доступ к элементу или через form (id или object) и radio group name, или посредством radio button (id или object).
var value = $RF('radio_btn_id'); 
var value = $RF('form_id', 'radio_grp_name');

Лично мне этот способ помог. Надеюсь, еще кому-нибудь пригодится.

Копирайты

Читать дальше »

пятница, 23 января 2009 г.

CSS переменные на Ruby on Rails

В посте про реализацию CSS переменных на PHP я вкратце упоминал про мою адаптацию этого способа для Ruby on Rails. Похоже, звезды уже выстроились нужным образом, что не забегая к гадалке, можно уже выложить коды.

Для реализации переменных CSS я использовал CSS-SSC от Shaun Inman. С некоторыми поправками исходя из особенностей моего приложения:
  1. Стили CSS у меня находятся внутри шаблона, а не в отдельном файле;
  2. Реализация разделения на переменные и константы мне не нужна;
  3. У меня только один тег style в шаблоне.

В отличие от оригинального CSS-SSC я решил определение переменных делать без значка доллара ($), а подстановку значений переменных -- с ним. Блажь у меня такая.
@server constants {
variableName: variableValue;
}

body {
color: $variableName;
}

Пример: необработанный HTML


<style type="text/css">
@server constants {
bodyBackgroundColor: #FFF;
bodyFaceColor: #000;
bodyFont: 11px/1.5em Arial, Helvetica, sans-serif;
}

body {
font: $bodyFont;
background-color: $bodyBackgroundColor;
color: $bodyFaceColor;
}
</style>

Перво-наперво, следует получить сами правила CSS, расположенные внутри тега <style type="text/css"></style>.

Для этого я создал следующую функцию.

Ruby-модуль css_ssc_module.rb


def ssc_get_style_contents(pattern, text)
matches = text.scan(pattern)
str_out = matches.to_s
return str_out
end

  • pattern (regular expression) -- шаблон RegExp по которому будет осуществляться выбор;
  • text (String) -- текст страницы, где будет осуществляться поиск и выборка.

Функция вернет текст, внутри элемента <style type="text/css"></style>.

Почему я не описал pattern внутри самой функции? Это регулярное выражение нам еще раз понадобится при подстановке сгенерированного CSS обратно на страницу. Правильнее будет ее определить только в одном месте.

Следующая функция выполнит следующие операции:
  1. Выберет все пары значений [variableName, variableValue] внутри блока @server constants {}
  2. Заменит в тексте CSS переменные $variableName их значениями variableValue;
  3. Удалит блок @server constants {}.

Ruby-модуль css_ssc_module.rb


def ssc_process_server_variables(css)
pattern = /@server\s+(?:variables|constants)\s*\{\s*([^\}]+)\s*\}\s*/i
pattern2 = /([^:\}\s]+)\s*:\s*([^;\}]+);/
str_out = css
if ( matches = css.scan(pattern) )
matches.each do |match|
vars = match[0].scan(pattern2)
vars.each{|var, value| css.gsub!("$#{var}", value)}
end
css = css.gsub!(pattern, '')
str_out = css unless css.nil?
end
return str_out
end

  • css (String) -- текст CSS.

Функция возвращает обработанный CSS.

Последняя функция объединяет две предыдущих. На вход получает «сырой» текст шаблона (страницы) и возвращает шаблон с обработанным CSS.

Ruby-модуль css_ssc_module.rb


def ssc_serve_styles(text)
pattern = /^\<style\ \b[^>]*>((\s|.)+?)\<\/style\>/i
css = ssc_get_style_contents(pattern, text)
css_out = ssc_process_server_variables(css)
text.gsub!(pattern, "<style type='text/css'>#{css_out}</style>")
return text
end

  • text (String) -- «сырой» текст страницы.


В результате обработки шаблона с переменными CSS функцией ssc_serve_styles(text), получим следующий код HTML.

Пример: обработанный HTML


<style type="text/css">
body {
font: 11px/1.5em Arial, Helvetica, sans-serif;
background-color: #FFF;
color: #000;
}
</style>


Читать дальше »

среда, 21 января 2009 г.

CSS переменные: реализация на PHP

Продолжаю тему переменных CSS. Простую реализацию для шаблонов XSLT я уже давал. Недавно нашел решение для PHP.

В нижеприведенном случае, CSS-переменные обрабатываются на стороне сервера. Меня это вполне устраивает. Более того -- я уже адаптировал этот способ для Ruby on Rails. Ну да не всё сразу.

Итак, CSS-SSC, что я понял примерно как CSS Server Side Constants”

Константы


Могут быть заданы как индивидуально:
@server constant constantName: constantValue;

так и в группе:
@server constants {
constantName1: constantValue;
constantName2: constantValue;
constantName3: constantValue;
}

Например, для такой записи CSS-SSC:
@server constants {
linkColor: #003366;
linkDecoration: underline;
}
a {color: linkColor; text-decoration: linkDecoration;}

Получим следующий CSS:
a {color: #003366; text-decoration: underline;}


Переменные


Переменные обрабатываются подобно константам, но из значение так же может передаваться через строку запроса:
sample.css?variableName=variableValue

Имена переменных начинаются со значка доллара ($).

Индивидуально:
@server variable $variableName: defaultVariableValue;

И всем табуном:
@server variables {
$variableName1: defaultVariableValue;
$variableName2: defaultVariableValue;
$variableName3: defaultVariableValue;
}


Пример CSS-SSC:
@server variable $bodyBgColor: #333;
body { background-color: $bodyBgColor; }

Даст такой CSS:
body { background-color: #333; }



Читать дальше »

понедельник, 19 января 2009 г.

Переменные CSS с использованием XSLT

Недавно озадачился введением в пользовательские шаблоны переменных CSS. Для чего нужны CSS-переменные? Да чтобы пользователь мог задать фирменные цвета, шрифты для своего шаблона и хранить их в одном, определенном месте (как бы это ни звучало). Чтобы впоследствии, если ему захочется что-то изменить, он мог перезадать их там же, а не рыскать по всему файлу CSS. Примерно так сделано в настройках Blogger, в разделе Макет - Шрифты и цвета.

Вот, пожалуй, самый тривиальный способ задания переменных CSS с использованием XSLT. В-частности xsl:variable. Для HTML этот способ не подходит.

Внутри HTML элемента head шаблона определите переменные и стили.

<!-- CSS Variables -->
<xsl:variable name="consoleEntityColor" select="'#B00080'" />
<xsl:variable name="consoleStringColor" select="'#7F007F'" />
<xsl:variable name="consoleKeywordColor" select="'#00007F'" />
<xsl:variable name="consoleActionColor" select="'#007F7F'" />
<xsl:variable name="consoleParamColor" select="'#C0A030'" />
<xsl:variable name="consoleCommentColor" select="'#007F00'" />
<xsl:variable name="consoleFont" select="'font-family: Courier New, Courier, monospace; font-size: 10pt'" />
<!-- Additional CSS -->
<style type="text/css">
.console {
width:300px; height:200px; border:1px solid <xsl:value-of select="$consoleActionColor"/>;
overflow:auto;
<xsl:value-of select="$consoleFont"/>;
}
.console .entity {
color: <xsl:value-of select="$consoleEntityColor"/>;
}
.console .string {
color: <xsl:value-of select="$consoleStringColor"/>;
}
.console .keyword {
color: <xsl:value-of select="$consoleKeywordColor"/>;
}
.console .action {
color: <xsl:value-of select="$consoleActionColor"/>;
}
.console .param {
color: <xsl:value-of select="$consoleParamColor"/>;
}
.console .comment {
color: <xsl:value-of select="$consoleCommentColor"/>;
}
</style>

Выведите HTML с использованием полученных стилей CSS:

<pre class="console">
<span class="comment"># Store home</span><br />
<span class="keyword">def</span> <span class="action">index</span><br />
<span class="entity">@store</span> = Store.find(params[<span class="param">:id</span>])<br />
<span class="entity">@title</span> = <span class="string">"Home - #{@store.name}"</span><br />
<span class="keyword">end</span>
</pre>

Выглядеть будет примерно так:


# Store home
def index
@store = Store.find(params[:id])
@title = "Home - #{@store.name}"
end

Читать дальше »

воскресенье, 18 января 2009 г.

Онлайн генератор разметки страницы CSS

И снова в копилку полезных ресурсов. Он-лайн генератор разметки страницы с использованием CSS (для ленивых). По завершении операции выдаст ссылки на два файла:
  • randomNumber.html -- собственно, сама страница с готовой разметкой HTML;
  • randomNumber.css -- «таблица стилей» CSS.

CSS Layout Generator

Читать дальше »