Постове 11 - 15 от 19 с таг Coding

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 30

Изненадващо е колко много хора не знаят, че в PHP файловете може да връщат резултат с return, който може да бъде прочетен с include / require. Даже го има и в документацията на PHP, eто пример:

// file1.php
return array('key' => 'value');

// file2.php
$arr = include 'file1.php';

echo $arr['key'];

// резултат: value

Като тук вместо include, може да се използва require (единствената разлика между двете е грешката, която възниква при проблем с отварянето на файла). Но аз лично предпочитам да използва include само когато php скрипта връща резултат, a require когато добавям нещо.

Тук трябва да се обърне внимание на 2 неща:

  1. require_once / include_once ако има return връщат стойност само първия път в който са извикани, а после нищо не връщат. Но те по принцип е добре да се избягват, особено ако се връща резултат
  2. всяка променлива / функция / клас / … която е била декларирана във include файла (file1.php в примера) си остава записана и достъпна. Така че ако в file1 се декларира $name = ‘Radoslav’; например във file2.php, $name ще е пак ‘Radoslav’. Затова е добре да се unset -ват всички ненужни глобални променливи, които не са нужни
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.

Sep 17

Преди време писах за PHP тестване в – PHP Unit testing. И сега искам да споделя един метод на тестване, с който използвах вече няколко пъти, което ми спести писане и като цяло се получи много добре.

Наскоро имах да тествам един базов клас и неговите наследници. Базовия клас се казва Session_Base и представлява абстрактен клас, в който съм си дефинирал основните операции за работа със сесии – set, unset, get, cut ( това е смесица между get и unset ), start, stop, fingerprint и други. Имам и няколко класа които го наследяват:

  • Session_Default – това си е просто нормалната php сесия и $_SESSION
  • Session_File – разликата с Session::Default е само че, този файл се грижи за записване и четене на session файловете
  • Session_Database – Session::File но за база данни
  • Session_Custom – Това е версията на стария ми php session клас ( много стара версия може да му видите на http://www.phpclasses.org/browse/package/2879.html )

Така това прави най-малко 4-5 TestCases. които във своята основа са едни и същи тест. В който се проверяват get/set/unset….open/write/read….

Затова реших да направя така:

// Test_TestCase си е един мой клас в който си слагам помощни работи и екстендва PHPUnit_Framework_TestCase
abstract class SessionBaseTest extends Test_TestCase {
private $sess;
function setUp();

function tearDown(){
$this->sess = null;
}

function testSettingVariable() { /* .... */ }
function testGettingVariable() { /* .... */ }
/* oще тестове */
}

Това ще ми служи като база за останалите тестове на сесията. И при едно наследяване PHPUnit ще изпълни и методите (тестовете).

// Test_TestCase си е един мой клас в който си слагам помощни работи и екстендва PHPUnit_Framework_TestCase
abstract class SessionDefaultTest extends Test_TestCase {
private $sess;
function setUp(){
$this->sess = new Session_Default();
}

// тестове специално към Session_Default();
}

Подобни тест класове си правя и за останалите класове. setUp() е абстрактен за да може в $this->sess да се сложи съответния клас.

Подобен метод използвах и в друга ситуация, разликата беше че имах редица от наследяващи се класове – RecordValidator -> FormValidator -> ActiveRecordValidator като всеки клас добавя нова функционалност, но основните действия, не трябваше да се променят. Тук направих TestCase за RecodValidator, после TestCase- за FormValidator го наследи и накрая ActiveRecordValidator наследи FormValidatorТest. Така си хванах сумати грешки и си спестих излишно писане на код ;)

Sep 15

Днес ми се наложи да направя нещо такова:

function action(){ /* ... code ... */ };
$(element).observe('click', action);
action.call($(element));

да декларираш функция която да се извика само един път, и при това, че я има като event-handler, е нещо, което винаги съм мразил да правя.
Но за щастие се сетих за “The “other” problem”, и по точно за тази част от поста:

Даже John-David Dalton е направил и Prototype версия : http://pastie.org/255119, от която най-много ми хареса допълнението на Element.fire, която я очаквам в новата версия на Prototype :)

Така че само с това малко парче код:

Element.addMethods({
	fire: Event.fire.wrap(function(proceed, element, eventName, memo) {
		element = $(element);
		var w, event, eventID;
		// ако eventName е например "click mouseover custom:event",
		// ще се симулират деиствията: click mouseover custom:event
		$w(eventName)._each(function(name) {
			// просто ако диствието е custom:event
			// си се вика нормалния Еvent.fire
			if (name.include(':'))
				return proceed(element, name, memo);

			// тук е първо се взема eventID-то на елемента
			// после ако е имало такова eventID се вземат event.cache всичките
			// event handle-и за този event
			eventID = (element._prototypeEventID || [null])[0];
			if (!eventID || !(w = Event.cache[eventID][name])) return false;

			// просто се прави "лъжлив" event обект
			event = Event.extend({ });
			event.eventName = name;
			event.memo = memo || { };

			// извикват се event handle-и
			w._each(function(wrapper) { wrapper(event) });
		});
		return element;
	})
});

Горния проблем се решава просто така:

$(element).observe('click', function action(){ /* ... code ... */ }).fire('click');

Което освен, че е по-кратко, е и по-красиво. / За по-разбираемо, не знам, защото аз си го разбирам де :) /