Показать сообщение отдельно
  #1 (permalink)  
Старый 23.09.2013, 22:33
_0_ _0_ вне форума
Аспирант
Отправить личное сообщение для _0_ Посмотреть профиль Найти все сообщения от _0_
 
Регистрация: 10.05.2013
Сообщений: 56

CSS генератор на JavaScript
Предлагаю обсудить скрипт, генерирующий 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>
Ответить с цитированием