Предлагаю обсудить скрипт, генерирующий CSS "на лету".
Всем понятна ущербность CSS в смысле отсутствия классов, констант и переменных (при полной поддержки во всех браузерах), особенно это ощутимо при создании какой-либо иерархии DOM-элементов (например, виджетов).
Сначала хотел сделать парсер, но в javascript проблемно подать многострочный текст при загрузке страницы, да и не силен я в парсерах, поэтому пошел объектным путем. Исходник стилевого листа - это javascript объект, с полями, заполненными по определенным правилам:
cначала идет блок констант, константы имеют флаг - первый символ "$";
потом идут классы, они начинаются с префикса "class_";
далее идут селекторы, их имена приходится заключать в кавычки.
Объект подается в генератор через конструктор
function css_object( obj ), конструктор возвращает объект, который имеет метод
.write(), записывающий элемент
STYLE в поток документа.
Классы и селекторы - это почти то же самое, они
наследуются расширяются одинаковым способом, также они имеют очень важную "вкусность" - вложенные селекторы, которые также могут расширяться классами. К дочерним селекторам прибавляется имя старшего селектора.
Константы довольно примитивны, одно правило может иметь только строку или одну константу.
Крайне чувствительный к лишним пробелам, неизбежно возникнет ошибка.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CSS JavaScript Object</title>
<script language='javascript'>
function css_object( obj ){
this.css_data=obj;
this.trace();
}
css_object.prototype={
// определение полей в прототипе не имеет значения, для наглядности
css_data:null,
// хранилища
vars:null,
classes:null,
selectors:null,
css_text:null,
// приемник строк
_css:null,
trace:function(){
var vars = this.vars={};
var classes = this.classes = {};
var selectors = this.selectors = {};
var _css = this._css = [];
var obj=this.css_data;
// разбор полей на типы
for( var p in obj ){
if( obj.hasOwnProperty(p) ){
var n;
if( p.indexOf('$')==0 ){
n=p.slice(1);
vars[p]=obj[p];
}else if( p.indexOf('class_')==0 ){
n=p.slice(6)
classes[n]=obj[p];
}else{
selectors[p]=obj[p]
}
}
}
// расширение классов
for( var class_name in classes ){
classes[ class_name ]=this._extend_obj( classes[ class_name ] );
}
// развертывание селекторов
var selectors2={}; // временное хранилище развернутых селекторов
var _this=this; // функция требует this
function deploy_selectors( srs ){
for( var name in srs ){
if( srs.hasOwnProperty(name) ){
var selector=srs[name];
var deferred={}; // хранилище отложенных селекторов
var d=false; // флаг наличия отложенных селекторов
// заодно расширение селектора
if( selector.extend ) {
selector = _this._extend_obj( selector )
delete selector['extend'];
}
for( var rule in selector ){
if( selector.hasOwnProperty(rule) ){
if( typeof selector[rule] == 'object' ){
deferred[name+rule]=selector[rule];
delete selector[rule];
d=true;
}
}
}
selectors2[name]=selector;
if( d ) deploy_selectors( deferred );
}
}
}
deploy_selectors( selectors );
selectors = this.selectors = selectors2;
// запись селекторов в массив строк
for( var name in selectors ){
if( selectors.hasOwnProperty(name) ){
var selector=selectors[name];
if( selector.comment ) {
_css.push('/* '+selector.comment+' */');
delete selector['comment'];
}
_css.push( name +' {' );
for( var rule in selector ){
if( selector.hasOwnProperty(rule) ){
var value=selector[rule];
if( typeof value !='object' ){
_css.push('\t'+rule+' : '+this._parse_value( value )+';');
}
}
}
_css.push('}\n');
}
}
this.css_text=_css.join('\n');
},
write:function(){
document.write('<style>\n');
document.write('/* Cгенерировано javascript */\n');
document.write( this.css_text );
document.write('</style>');
},
// расширение объектов стандартным способом
_extend_obj:function( obj ){
if( !obj.extend ) return obj;
var top_obj=obj;
var classes_str=obj.extend;
obj={};
var classes=this.classes;
var chain=[];
var parents=classes_str.split(',');
for( var i=0, l=parents.length; i<l; i++ ){
var class_name=parents[i];
if( classes[class_name] && typeof classes[class_name]=='object' ){
chain.push( classes[class_name] )
}else{
var s='Не найден класс '+class_name;
alert(e);
throw e;
}
}
chain.push( top_obj );
for( var i=0, l=chain.length; i<l ;i++ ){
this._extend( obj , chain[i] );
}
return obj;
},
_extend:function( obj, ext ){
for( var n in ext ){
if( ext.hasOwnProperty(n) ){
if( n!='extend' ){
var v=ext[n]
if( v === null ){
delete obj[n];
}else{
obj[n]=v;
}
}
}
}
return obj;
},
_parse_value:function( value ){
if( value.indexOf('$')==0 ){
var vars=this.vars;
if( vars[value] ){
return vars[value];
}else{
var e='Не найдена переменная '+value;
alert(e);
throw e;
}
}else{
return value;
}
},
}
</script>
<script language='javascript'>
css=new css_object({
// константы
$widget_border:"1px solid #888",
$widget_color:"green /* $widget_color */",
$widget_background_color:"yellow",
$widget_margin:"3px /* $widget_margin */",
$widget_padding:"1px /* $widget_padding */",
$widget_hover_color:"red /* $widget_hover_color */",
// классы
class_widget:{
"border":"$widget_border",
"color":"$widget_color",
"background-color":"$widget_background_color",
"font-family":"Arial",
},
class_widget_block: { extend:"widget",
"display":"block",
"width":"200px",
"height":"50px",
"margin":"3px",
"padding":"3px",
":hover":{
comment:"Обработка наведения курсора на стандартный блок widget",
"border-color":"$widget_hover_color"
}
},
class_widget_hover:{
"text-decoration":"none",
"color":"blue",
':hover':{
comment:"Обработка наведения курсора",
"text-decoration":"underline",
"color":"$widget_hover_color",
},
},
class_clear_list:{
comment:"Чистка стиля списков",
"display":"block",
"padding":"0px",
"margin":"0px",
"list-style-type":"none",
},
// селекторы
".widget":{ extend:"widget_block",
" .label":{
"font-size":"12px"
},
" a":{ extend:"widget_hover",
"font-size":"12px",
"color":"blue"
},
" button":{ extend:"widget_hover",
"font-weight":"bold"
},
" div.menu":{
comment:"Менюшка на css",
"position":"relative",
"display":"inline-block",
" ul,li":{ extend : "clear_list" },
" ul":{
comment:"Все выпадающие блоки",
"position":"absolute",
"width":"150px",
"background-color":"$widget_background_color",
"display":"none",
},
" .label":{ extend : "widget_hover",
"position":"relative",
"line-height":"20px",
"border":"$widget_border",
"padding":"0px 5px 0px 5px",
"cursor":"pointer",
":hover > ul":{
comment:"Выпадание вложенного блока",
"display":"block",
"top":"-1px",
"left":"100%"
}
},
" > ul":{
comment:"Первый элемент меню",
"top":"20px",
"left":"0px",
},
":hover > ul":{
comment:"Выпадание первого элемента",
"display":"block",
},
}
},
"textarea#t":{
comment:"Элемент вывода",
"width":"600px",
"height":"300px"
},
})
css.write()
</script>
</head>
<body>
<div class='widget'>
<span class='label'>просто виджет '.widget'</span><br />
<a href='#'>Ссылка</a>
</div>
<div class='widget'>
<span class='label'>виджет с кнопкой</span><br />
<button>Button</button>
</div>
<div class='widget'>
<span class='label'>виджет с css меню</span><br />
<div class='menu'>
<span class='label'>Меню</span>
<ul>
<li class='label'>Пункт 1</li>
<li class='label'>Пункт 2 ...
<ul>
<li class='label'>Пункт 2-1</li>
<li class='label'>Пункт 2-2</li>
<li class='label'>Пункт 2-3 ...
<ul style='width:100px'>
<li class='label'>Пункт 2-3-1</li>
<li class='label'>Пункт 2-3-1</li>
</ul>
</li>
<li class='label'>Пункт 2-4</li>
</ul>
</li>
<li class='label'>Пункт 3</li>
<li class='label'>Пункт 4</li>
</ul>
</div>
</div>
<hr />
<textarea id='t' -style='width:500px;height:300px'></textarea>
<script language='javascript'>
document.getElementById('t').value=css.css_text;
</script>
</body>
</html>