5 поста в категория Control Depo

Feb 07

Като цяло тази седмица мина под знака на Rails 3, на който излезе бета версия.

Понеделник 01.02.2010

SafeBuffers and Rails 3.0 – в Rails 3 има вградена xss защита, и Yehuda Katz обяснява последните промени по тази система и защо са се наложили.

8 Chrome Extensions For Web Developers – Напоследък Firefox започва да се държи все по зле и леко да изостава от браузъри като Chrome и Safari. И докато в Safari все още ми липсват 2-3 малки неща, то Chrome може в много скоро време да стане предпочитания ми браузър.

Вторник 02.02.2010

Google Apps Drop IE6 Support – Е време беше. След скорошната атака срещу Google извършена с IE6, Google решиха да дръпнат шалтера на това нещо. В Германия вече го забраниха, така че още малко живот му остава. И усещам как скоро ще се вдигнем мерника към IE7.

One-click Minifier Gadget (OMG) – initial checkin – Българското Yahoo! гуру Стоян Стефанов пусна много полезен инструмент за минимизиране. Кода го има в github. ( Статията е от 31 Януари, но чак във Вторник я видях )

Basic Cappuccino Tutorial - Добър на урок за Cappuccino. Което е добре защото, един от най-големите проблеми на Cappuccino, според мен, е липсата на добра документация и уроци.

How Internet Explorer 8 document mode affects JavaScript – Оказва се че в IE8 имало доста промени по самия JavaScript.  Nicholas C. Zakas разяснява голяма част от тях.

The touch action – В България до голяма степен сме 1-2 години назад в IT отношение. Така че е доста лесно да се предскаже че в следващите години и у нас както навън ще навлязат все повече “умни телефони” и особено такива с touch screen (то май всички са такива). Затова е важно да се знае как може да се работи със touch събитието. В Quirksmode имаше цяла серия за тези събития през седмицата.

an HTML5 offline image editor and uploader application – Наскоро започнах да правя първите залежи за новата ни cms система ControlDepo 4. И мисля в нея да вградя нещото такова в една или друга степен.

HipHop for PHP: Move Fast – Това беше новината на деня. Facebook пуснаха HipHop, което компилира (или по-точно трансформира) PHP кода до оптимизиран C++ код, който след това се компилира с g++. От което е излишно да казвам какви са ползите като скорост и CPU. За повече подробности самия пост и това видео.

Явно вторник е бил много “натоварен” ден. :)

Сряда 03.02.2010

YUI Theater — Douglas Crockford: “Crockford on JavaScript — Volume 1: The Early Years” – Когато Douglas Crockford, говори и Чък Норис слуша :) . В случая говори за история на JavaScript и програмните езици като цяло. Първите 10-15 са необичайно скучни. Главно защото говори за първите компютри и темата е повече насочена към хардуера от колкото към софтуера. Но после нещата се оправят.

Stop Being an Idiot – Колкото пъти сме се оправдавали с това че потребителите са “идиоти”, е време е да спрем.

Четвъртък 04.02.2010

The Rails Module (in Rails 3) -  Rails 3 идва с доста променен Rails module, в който има доста полезни методи. От тази статия може да се научат доста странични неща за Rails като цяло.

Също интересен факт е и че Basecamp стана на 6 години. Като един ден преди това стори и Facebook.  Което като се замисли човек е много странно съвпадение. Защото никои не може да се отрече че и двата продукта косвено или директно промениха мрежата.

Петък 05.02.2010

SD Ruby – Episode 077: Building Your Own Hosting Environment – това стои във readera ми от доста време, но чак в Петък имах време и повод да го гледам. Доста добро ми се стори и ако някои мисли сам да си хоста Rails приложенията ( и даже каквото и да е друго сам да си хоства) е много добра начална точка.

Преч седмица най-сетне намерих една от най-запомнящите реклами, който съм гледал (незнайно защо бях останал с впечатлението  че е за Ferrari, а тя била за Porsche ):

May 02

Вчера отделих целия ден да направя някои промени по блога, като основно имах доста забележки към темата (iTheme), която ползвам.

Първо започнах да правя малки промени по дизайна

  • сложих да се виждат таговете към даден пост
  • промених (с малко помощ от колега) изцяло извеждането на коментарите
  • формата за добавяне на коментар претърпя малки графични промени
  • тема вече е изцяло на български език (тук таме може да е останало нещо чуждоезично, но малко по-малко що променям)
  • доста по-добра 404 страница, за която използвах от Google Webmaster Tools - Enhance 404 pages
  • промени извеждането на постовете при търсене.

Аз в мрежата

После започнах промени по секциите

  • Добавих нова страница “Аз в мрежата“, в която има информация за част от различните ми регистрации из мрежата. Скоро ще има още връзки, просто на този етап нямам подходящи лога за тази страница (трябваше да има и страница “Аз съм”, но за нея не остана време :( )
  • Добавих “Подобни постове” към всеки пост, като за него използвам плъгина Contextual Related Posts
  • Изтрих от sidebar-a мета информацията и на нейно място сложих таговете в сайта
  • Преминах през Google Webmaster Tools и пооправих някой връзки в сайта, които не работеха, също така промених и няколко meta description-и и други дребни неща

Промени по кода

И след това започнах да работя по интересната част. Докато оправях темата забелязах, че колкото и да е красива от долу като код е просто … ужасна. Още повече самия Wordpress не използва нещо като Smarty, и го кара на голо php (в този пост – “Be smart with smarty“, съм си казал мнението за Smarty). Всички шаблони на темата общо взето изглеждаха така:

<?php get_header(); ?>
	<div id="content">
		// кода за съответния шаблон
	</div>
	<div id="footer">
		<a href="...">WP Theme</a> &amp;
		<a href="...">Icons</a> by <a href="...">N.Design Studio</a>
	</div>
</div>
<?php get_sidebar(); ?>
<?php get_footer(); ?>

след малко refactoring в шаблон кода стана така, като преместих общите части в хедъра и футера:

<?php get_header(); ?>
	// кода за съответния шаблон
<?php get_footer(); ?>

Мина ми през главата, дали да не направя някои плъгин за Wordpress, който позволява ползването на Smarty и/или layout файлове, подобни на тези които използвам от години в ControlDepo и които ги има в Rails, но в крайна сметка реших, че през лятото най-вероятно ще мина на някое мое блог решение.

След като подредих  и пренаписах шаблоните, се насочих към самата html структура, в която беше пренебрегнато ползването на ul елементи и други дребни неща който ме дразнеха. Но най-големия проблем от който най-вероятно страда доста SEOто на NeXt е че липсваше h1 елемент (т.е. имаше го но просто винаги беше просто NeXt), а той трябва все пак да е заглавието на самата страница.

JavaScript / CSS / Images

Поради използването на много плъгини се беше натрупало огромно количество външни javascript и css файлове, което много вреди на цялостното зареждане на сайта. Така че минах и събрах всичките css файлове в един all.css (останаха само print.css и css файловете от Highlight Source Pro плъгина, но там още не искам да пипам).

С JavaScript нещата седяха малко по сложно поради това че използвах plugin за live търсенето и за още 2-3 малки неща, решението беше да се отърва от всички плъгини и да си напиша собствени версии използвайки ControlDepo 3 Widgets.  От него използвах тези компоненти:

Отделих javascript-a в два файла – all.js и frontend.js ( както права във всичките си проекти напоследък ), Във all.js са библиотечните файлове – Prototype, Script.aculo.us, no.ie6.js, ControlDepo 3 Widgets а във frontend.js са скриптовете специално за блога ми livesearch, widgets и други малки javascript глезотии. По-важното е в случая е това че вече имам стабилна основа върху която да надграждам в бъдеще.

Всички javascript файлове са най-долу, така че няма смисъл да правя dom:loaded а мога директно да изпълнявам кода си. Също така вече и целия код е unobtrusive, и поне на този етап ако даден посетител няма javascript или му е изключен, всичко в сайта ще си работи.

Последната стъпка беше оптимизацията на снимките за това използвах просто ruby gem-a – smusher, който прекарва всяка снимка през smush.it. Така отстраних с около 40 KB размера на всички картинки от блога.

Финални думи

Като цяло за себе си съм си доволен от работата, която свърших по блога. Останаха някои неща за бъдещето:

  • live comment preview
  • най-сетне да имам about страница
  • да взема под IE да видя как се вижда NeXt
  • да се по съберат снимките в css spirites
  • почистване на css файловете, т.е. изтриване на излишните стилове и оптимизация
  • gzip на css / javascript файловете

п.п. от цялото тази работа около Wordpress, ми хрумнаха две идеи за plugin, първия да е Smarty, а втория да е нещо подобно на sprockets което събира всички css / javascript във един файл gzip-ва го, добавя му etags и т.н. Незнам дали е възможно да се направи такова нещо с Wordpress, ако някой има желание за нещо такова, с удоволствие бих му помагал.

Nov 15

Това е втория пост за ControlDepo 3 WidgetsBehaviors (по план имам още един :) ).

Преди време ми се наложи да “освежавам” един стар проект използващ една от първите версии на CD3.Behaviors. Тази стара версия имаше само нормален селектор | event-селектори. Там забелязах че много често ползвам pattern (това ако някой на български ми го каже как е, ще съм много благодарен) а именно:

CD3.Behavoirs({
	'#container': function(contaner){
		contener.select('a').invoke('click', doSomeAction);
		contaner.select('a').invoke('mouseover', doSomeOtherAction);
		// .. и така си избирам всичките под елементи на #container и да им добавям event handler
	}
});

Това е примерен код за простичка javascript галерия, която има два бутона предишна / следваща картинка както и списък с thumbnail-и от който пак може да се покаже голямата снимка.  selectImage просто от a таг взема href атрибута и го показва като снимка с ефект и т.н, но то не е важното в случая.

CD3.Behaviors({
	// code
	'#gallery': function(){
		// при натискане на стрелката за предишна снимка да ...
		this.down('a.prev').observe('click', function(){
			var thumbs	 = $('thumbs'),
				selected = thumbs.down('a.selected');

			// покажи или предишнина или послената снимка
			selectImage(((selected &amp;amp;amp;amp;&amp;amp;amp;amp; selected.up('li').previous('li')) || thumbs.select('li').last()).down('a'));
		});

		// при натискане на стрелката за следваща снимка да ...
		this.down('a.next').observe('click', function(){
			var thumbs	 = $('thumbs'),
				selected = thumbs.down('a.selected');

			// покажи следващата или първата снимка
			selectImage(((selected &amp;amp;amp;amp;&amp;amp;amp;amp; selected.up('li').next('li')) || thumbs.select('li').first()).down('a'));
		});

		// тук правя много прост event delegation
		// избирам натиснатия a таг и викам selectImage с него
		$('thumbs').observe('click', function(e){
			e.stop();
			var a = e.findElement('a');
			if (a) selectImage(a);
		});
	}
	// code
});

Естествено някой от случаите могат да бъдат избегнати с event-delegation | инстанциране на класове, но  ми дойде друга, по-добра (според мен) идея. Добавих нов метод към CD3.Behavoirs – when. CD3.Behavios.when приема 2 параметъра 1вия е selector, а другия е hash object с behavoirs. Идеята се намерят елементи отговарящи на подадения селектор, се добавят и съответните behavoirs. Единственото по особеност тук е че selector-ите на behavoir-ите не се търсят в контекста на document а в контекста на елемента на който отговаря selectora подаден като първи аргумент.

И така със CD3.Behavios.when примера със новинарската джаджа изглежда така:

// ако съществува елементи #galley
// добави слените behaviors от контекста на #gallery
CD3.Behaviors.when('#gallery', {
	// при натискане на стрелката за предишна снимка да ...
	'a.prev:click': function(){
		var thumbs	 = $('thumbs'),
			selected = thumbs.down('a.selected');

		// покажи или предишнина или послената снимка
		selectImage(((selected &amp;amp;amp;amp;&amp;amp;amp;amp; selected.up('li').previous('li')) || thumbs.select('li').last()).down('a'));
	},
	// при натискане на стрелката за следваща снимка да ...
	'a.next:click': function(){
		var thumbs	 = $('thumbs'),
			selected = thumbs.down('a.selected');

		// покажи следващата или първата снимка
		selectImage(((selected &amp;amp;amp;amp;&amp;amp;amp;amp; selected.up('li').next('li')) || thumbs.select('li').first()).down('a'));
	},
	// избирам натиснатия a таг и викам selectImage с него
	'thumbs:click': {
		a: function(е){
			е.stop();
			selectImage(this);
		}
	}
});

Както се вижда кода е почти същия но сега изглежда доста по-добре и по-четим. В последните ми няколко по-натоварени с javascript проекти си структурирам джаджите в отделни извиквания на when и нещата изглеждат доста добре.

Oct 04

Днес имах малко свободно време и реших да погледна малко един проект, и по-точно javascript честа от него. И се спрях на един простичък Tab панел, нищо особено, но нещо не ми хареса как изглеждаше JavaScipt-а. HTML изглежда така:

<div class="tab-panel">
	<ul class="tab-header">
		<li class="selected">tab 1</li>
		<li>tab 2</li>
		<li>tab 3</li>
	</ul>
	<div class="tab-content">tab 1 content</div>
	<div class="tab-content" style="display: none;">tab 2 content</div>
	<div class="tab-content" style="display: none;">tab 3 content</div>
</div>

За да направя това таб панел използвах просто тази функция:

function tabulize(panel){
	// слагам div.tab-content вместо .tab-content,
	// защото когато селектора има само клас селектира всички елементи и ги проверява,
	// а ако се сложи div.tab-content се селектират се всички div-ове и те се проверяват,
	// което е по-бързо
	var elements	= panel.select('div.tab-content'),
		buttons		= panel.down('ul').select('li');

	// дефинирам тази функция тук,
	// защото искам да имам достъп до elements, buttons
	function activate(item, key){
		// тук 1во се маха клас selected от всички бутони,
		// и се скриват всичките div.tab-content
		buttons.invoke('removeClassName', 'selected');
		elements.invoke('hide');

		// после се показва избрания елемент и се слага клас selected на избрания бутон
		elements[key].show();
		item.addClassName('selected');
	}

	// на всеки бутон се добавя eventhandler за click,
	// който ще подава на activate избрания елемент и неговия номер във buttons масива
	buttons.each(function(item, key){
		item.observe('click', activate.curry(item, key));
	});
}

Това е доста простичко, но и трудно за промени като динамично добавяне на съдържание, ефекти и други. Затова реших да го направя малко по-OOП, като запазя основните идеи:

var TabPanel = Class.create({
	// това е конструктора
	initialize: function(panel){
		panel = $(panel);
		// избираме отново elements и buttons, но ги записваме като инстанс променливи
		this.elements = panel.select('div.tab-content');
		this.buttons = panel.down('ul').select('li').each(function(){
			// единствената разлика е че active не е private функция и инстанс метод
			// и за това тук ползваме bind, а не curry
			item.observe('click', this.activate.bind(this, item, key);
		}.bind(this));
	},
	// active, вече е метод и само "this." e разликата
	activate: function(item, key){
		this.buttons.invoke('removeClassName', 'selected');
		this.elements.invoke('hide');
		this.elements[key].show();
		item.addClassName('selected');
	}
});

Сега става малко по-тежко, но и доста по-extendable и податливо на бъдещи промени. Но тук видях нещо, което пак не ми се хареса е че колкото таб бутони имам толкова пъти и в двете версии викам observe и реших да видя как ще изглежда това, като добавим малко event-delegation, за което трябваха само две промени:

var TabPanel = Class.create({
	initialize: function(panel){
		panel			= $(panel);
		this.elements	= panel.select('div.tab-content');
		this.buttons	= panel.down('ul').select('li');
		// първо променяме тук
		// слагаме click eventhandler на ul (списъка с бутоните)
		// вътре проверяваме дали li елемента, който сме натиснали (ако има такъв)
		// е във масива с бутоните, ако е така активираме табулацията с този номер
		panel.down('ul').observe('click', function(e){
			var key = this.buttons.indexOf(e.findElement('li'));
			if (key != -1) this.activate(key);
		}.bind(this));
	},
	// второто нещо което променяме е тук
	// махаме item параметъра, и на го заменяме с this.buttons[key]
	activate: function(key){
		this.buttons.invoke('removeClassName', 'selected');
		this.buttons[key].addClassName('selected');
		this.elements.invoke('hide');
		this.elements[key].show();
	}
});

И поне на този етап съм доволен, при нужда мога да добавя destroy, addTab и други неща :) А как инстансирам TabPanel-а ? Ами със CD3.Behaviors:

CD3.Behaviors({
	'div.tab-panel': TabPanel
});
Oct 03

Наскоро направих, нещо което от доста време планирам – да събера публикувам част от JavaScript нещата които ползвам, като opensource. Така че ето ги ControlDepo 3 Widgets:

http://github.com/RStankov/controldepo-3-widgets/tree/master

Тук смятам да събера, основните JavaScript неща, които имам като custom form полета, ефекти, prototype допълнения и други подобни. И също така ще публикувам във блога и статии с които да описвам как се работи с дадения компонент (така най-сетне ще имам документация). И така първия и може би любимия ми :

CD3.Behaviors

CD3.Behavoirs e вдъхновен от CSS event:Selectors на Justin Palmer и LowPro на Dan Webb ( както и Behavior на  Ben Nolan, който вече го няма). Общо взето от около една и половина го ползвам и развивам и мисля че доста полезен. Работата на CD3.Behavoirs, условно се разделя на 4ри части:

нормален селектор | event-селектори | event-delegation | инстанциране на класове

Нормалния селектор просто селектира определения css селектори и вика върху всеки елемент подадената функция, като съответния елемент  се предава като първи аргумент и също така функцията се bind-ва към него т.е. като в подадената функция this е този елемент за който тя се извиква. Ето един малък пример:

CD3.Behaviors({
'#container1': function(){
this.insert('<strong>текст</strong>');
},
'#container1 a': function(a){
a.observe('click', function(){ alert('#container1 a clicked'); })
},
'#container1 span': function(){
alert('This will not excecute');
}
});

Горния пример  има 3 селектора:

  1. #container1 – избира елемент с id = container1 и после в него добавя <strong>текст</strong> вътре в него
  2. #container1 a – избира всеки а елемент от container1 и добавя click event към него ( тук а се подава като аргумент, но може да се напише и this.observe )
  3. #container1 span – просто показва че ако няма елемент отговарящ на селектора, функцията не се вика

Event-селекторa разглежда селектора на 2 части {селектор част}:{event част}. Като намира всички елементи от дадения селектор и им добавя event listener-и (чрез Event.observe). Действията са много подобни на тези на CSS event:Selectors на Justin Palmer, да не кажа същите, с някои подобрения.

CD3.Behaviors({
'#container2 a:click': function(){
this.toggleClassName('clicked')
},
'#container2 a.mouse': {
mouseover: function(){
this.innerHTML = 'mouseover';
},
mouseout: function(){
this.innerHTML = 'mouseout';
}
}
});

Какво става тук:

  1. #container2 a:click – избира всички а-та от #container2 и им добавя при click event да си сменят className -а
  2. #container2 a.mouse- е малко по-интересно. То селектира всички а-та с клас “mouse” в #container2 и започва да наблюдава (observe) две действия:
    • mouseover – просто добавя текст “mouseover” във а-тa
    • mouseout -просто добавя текст “mouseout” във а-то

Горния пример със нормален селектор би изглеждал така:

CD3.Behaviors({
'#container2 a': function(a){
a.observe('click', function(){
this.toggleClassName('clicked');
});
},
'#container2 a.mouse': function(a){
a.observe('mouseover', function(){
this.innerHTML = 'mouseover';
});
a.observe('mouseout', function(){
this.innerHTML = 'mouseout';
});
}
});

Но просто има твърде много излишен код тук, а и в като се пише JavaScript – the size matters!

Event-delegation – тук става малко по “сложно” … за обяснение.  По принцип Event-delegation-a е доста лесен, и особено при по-динамични и натоварени javascript приложения си е задължителна практика. И понеже аз доста често го използвах реших да го вкарам във CD3.Behaviors като ползвам нещо подобно на Event.delegate от LowPro. Като основната ми цел беше да го “скрия” така да не се натрапва и според мен стана доста добре:

CD3.Behaviors({
'#container3:click': {
'span': function(){
alert('span was clicked, span innerHTML is "' + this.innerHTML + '"');
},
'a': function(){
alert('link was clicked, span innerHTML is "' + this.innerHTML + '"');
},
'div': function(){
alert('nothing was clicked');
}
},
});

Така, какво става тук ? Ами, със нормален event-selector избираме #container3 и му добавяме click event. Само, че когато се натисне #container3 започва да се проверяват подадените селектори – span, a, div, в случая, и когато натиснатия елемент отговаря на някоя селектор се вика съответната функция, който е била зададена към селектора ( като даже и scope-a на функцията се сетва да е съответния елемент, така че ако е натиснат ’span’ елемент this ще е този елемент)

По начина по който съм направил event-delegation-a може да се пишат и такива неща:

CD3.Behaviors({
'#container3': {
mouseover: {
'span': function(){
this.addClassName('clicked');
},
'a': function(){
this.addClassName('clicked');
}
},
mouseout: {
'span': function(){
this.removeClassName('clicked');
},
'a': function(){
this.removeClassName('clicked');
}
}
}
});

Което е все едно да имаме 2 event-селектора – #container3:mouseover и #container3:mouseout, но горния код е доста по-бърз защото селектираме само ведъж #container3 :) да не говорим че е и доста по ясен.

Инстанциране на класове. Едно от най-яките неща в LowPro бяха Behavoirs класовете, обаче така и не ги използвах никъде, а и предпочитам да ползвам нормални prototype класове. Затова направих най-нормалното, което ми се виждаше, да направя всеки нормален prototype клас (без да променям нищо в prototype) да работи със CD3.Behaviors:

var TestWidget = Class.create({
initialize: function(element, options){
this.value = options || 0
element.observe('click', this.click.bind(this));
},
click: function(){
alert('widget with ' + this.value + ' was clicked');
}
});
CD3.Behaviors({
'#container4 a.first': TestWidget,
'#container4 a.second': [TestWidget, 5]
});

Това, може да се напише и по този начин :

CD3.Behaviors({
'#container4 a.first': function(){
new TestWidget(this);
},
'#container4 a.second': function(){
new TestWidget(this, 5);
}
});

Единственото, лошо тук е че [TestWidget, 5] не може да приема (за сега) повече от един параметър, който играе ролята на options. А и повечето Widget класове, които ползвам са само със element и options аргументи.

Това горе-долу са основните части от CD3.Behaviors. В идните сигурно ще напиша как работи CD3.Bahaviors.when и как аз обикновенно използвам CD3.Bahaviors.