Dynamiczny CSS z poziomu JavaScript

Autor: nme · poniedziałek, 7 Wrzesień, 2009 · Brak komentarzy ·

Jak zapewne większość developerów używających JavaScriptu, mam swoją własną biblioteczkę skryptów oraz dodatkowych narzędzi która stanowi dla mnie formę bootstrapu do tworzenia nowej aplikacji. Z każdym projektem biblioteczka rośnie i staje się bardziej dojżała. Przymierzam się od jakiegoś czasu do opublikowania całego bootstrapu, bo uważam, że jest warty uwagi, ale jakoś nie miałem do tej pory zbytnio na to czasu.

W tym tekscie poniżej przedstawie jeden ze skryptów który należy do mojej biblioteczki. Załączam go standardowo do każdej swojej aplikacji, szczególnie teraz, kiedy zacząłem przygotowywać swoje własne rozszerzenia JQuery UI.

Swobodniejsze podejście

Bardzo lubie dbać o to, aby struktura aplikacji była zwięzła, łatwa do ogarnięcia oraz estetyczna. W dobie Javascriptu mamy coraz to większe możliwości aby samemu sobie ustrukturyzować aplikacje.

Wielu doświadczonych developerów zakłada, sztywny podział — czyli, że wygląd powinien być w plikach CSS, funkcjonalność w JavaScript, a dane na serwerze. Oczywiście z trzecim się zgadzam, ale z podziałem na wygląd i funkcjonalność — już nie koniecznie.

Przykładowo — w naszym pliku CSS wykorzystujemy -moz-border-radius i -webkit-border-radius, dzięki czemu mamy zaokrąglone rogi wszędzie, poza Internet Explorerem. Musimy dodać zaokrąglone rogi w Explorerze. Co począć? Nabrudzić sobie w CSSie? A później za pomocą JQuery sprawdzić wersję przeglądarki i ustawić te klasy? Można i tak. Możemy jednak sprawdzić czy mamy do czynienia z IE i jeśli tak — dopiero wtedy dodać nowe klasy CSS.

Dynamiczne ładowanie lub generowanie CSS nie jest czymś bez czego nie możnaby się obyć, ale dzięki warstwom abstrakcji możemy sobie nie śmiecić w plikach z layoutem.

Poniższy kod działa bez problemu w każdej zaawansowanej przeglądarce WWW oraz w Internet Explorerze :)

/*
 * CSS Gen
 * Copyright (c) 2009 nme.pl
 * Dual licensed under MIT and GPL.
 *
 * from scratch, based on article http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
 *
 * Example usage:
 *
 * var css = new CSS('dynamic'); // Create new stylesheet
 * css.replace('.asd',{'color':'green'}); // If there is no asd class definition yet, it will be created
 *
 */
var CSS = function(name,url) {
	if (name === undefined) {
		return false;
	}
	var styles = document.styleSheets;
	if (!styles) {
		return null;
	}
	this.find_style = function(name) {
		if (styles) {
			for (var i in styles) {
				if (styles[i].title === name) {
					return styles[i];
				}
			}
		}
		return null;
	};
	var create_style = function(name) {
		var node = document.createElement('style');
		node.type = 'text/css';
		node.rel = 'stylesheet';
		node.media = 'screen';
		node.title = name;
		document.getElementsByTagName(”head”)[0].appendChild(node);
		return node;
	};
	var load_style = function(name,url) {
		var node = document.createElement('link');
		node.type = 'text/css';
		node.rel = 'stylesheet';
		node.href = url;
		node.media = 'screen';
		node.title = name;
		document.getElementsByTagName(”head”)[0].appendChild(node);
		return node;
	};
	var style = this.find_style(name);
	if (style === null) {
		if (url === undefined) {
			style = create_style(name);
		} else {
			style = load_style(name,url);
		}
	}
	var sheet = null;
	if (style !== null) {
		if (style.styleSheet !== undefined) {
			sheet = style.styleSheet;
		} else {
			sheet = style.sheet;
		}
	}
	this.index = function(search) {
		search = search.toLowerCase();
		var i=0;
		var rule=null;
		if (sheet === undefined) {
			return null;
		}
		do {
			if (sheet.cssRules) {
				rule = sheet.cssRules[i];
			} else if (sheet.rules) {
				rule = sheet.rules[i];
			}
			if (rule) {
				if (rule.selectorText.toLowerCase() == search) {
					return i;
				}
				i++;
			}
		} while (rule);
		return null;
	};
	this.rule = function(search) {
		search = search.toLowerCase();
		var i=0;
		var rule=null;
		if (sheet === undefined) {
			return null;
		}
		do {
			if (sheet.cssRules) {
				rule = sheet.cssRules[i];
			} else if (sheet.rules) {
				rule = sheet.rules[i];
			}
			if (rule) {
				if (rule.selectorText.toLowerCase() == search) {
					return rule;
				}
				i++;
			}
		} while (rule);
		return null;
	};
	this.add = function(search, body) {
		if (sheet === undefined) {
			return null;
		}
		if (typeof body === 'object') {
			var rules='';
			for (var i in body) {
				if (body.hasOwnProperty(i)) {
					rules = rules+i+':'+body[i]+';\n';
				}
			}
			body = rules;
		}
		if (!this.rule(search)) {
			if (sheet.cssRules) {
				sheet.insertRule(search+' {'+body+'}', 0);
			} else if (sheet.rules) {
				sheet.addRule(search, body);
			}
		}
		return this.rule(search);
	};
	this.remove = function(search) {
		var i = this.index(search);
		if (i !== null) {
			if (sheet.cssRules) {
				sheet.deleteRule(i);
			} else if (sheet.rules) {
				sheet.removeRule(i);
			}
		}
	};
	this.replace = function(search, body) {
		this.remove(search);
		return this.add(search,body);
	};
};

Jak tego używać? Na początku przygotowujemy nowy, dynamiczny stylesheet:

var css = new CSS('dynamic');

Jeśli klasa asd nie została jeszcze zdefiniowana, robimy to teraz oraz ustawiamy jej następujące atrybuty:

css.replace('.asd',{'color':'green'});

Pseudoklasa CSS umożliwia również dynamiczne ładowanie CSS'ów czy usuwanie poszczególnych reguł styli. W razie wątpliwości — zachęcam do zaglądania do kodu oraz do kontaktu ze mną :)

Przykładową aplikacją, która dość intensywnie korzysta z tego fagmentu kodu jest http://mlchat.appspot.com — czat, który uruchomiłem na Google App Engine, który jednak zarzuciłem, bo nie znalazłem już sił po stworzeniu go na jego odpluskwianie i promocję ;) Wygląd aplikacji miał być z zamierzenia łudząco podobny do Google Web Toolkit'u i co za tym idzie takich aplikacji jak Google Reader, ale tak naprawdę było to JQuery i trochę pracy :) Zachęcam do poklikania — ludzi tam raczej nie ma, jak sugerowałyby liczby, to tylko duchy — stare sesje się nie kasują jak powinny ;)

Zostaw komentarz