Постове 1 - 5 от 9 в категория Learn

Jan 19

В последно време много се дразня на хората, които си мисля, че изграждането на един сайт е супер проста и лесна работа. И че от техническа гледна точка един сайт (или даже уеб приложение) е в пъти по-прост от едно десктоп приложение. Затова реших да драсна няколко реда, за някои от важните неща за който трябва да се мисли в един сайт от frontend страната.

Като начало – html

За създаването на един сайт, трябва html. По принцип html е просто нещо, но и той като php-то е “измамно лесен”. За една добра html структура какво трябва да се гледа?

Първо дали е семантична – всеки  html си има някакво значение. И това значение трябва да се ползва. Въпреки, че понякога това е много трудно. Примерно тага за списък <ul> трябва да се използва всеки път, когато има меню или списък от каквото и да е естество.  За всяка форма към всеки елемент трябва да има <label> със съответния for атрибут, оказващ за кои елемент е този label. И т.н.

След това идва и seo-то. Много е важно html да е построен така, че една търсеща машина (google например) много лесно да може да се ориентира в самия сайт какво има. От една страна това е свързано със семантиката на html. Примерно е важно заглавието на страницата да е със <h1>, да има коректни meta тагове (по възможност различни за всяка страница), всяка връзка, ако няма текст, да има поне title атрибут. Както и всяка картинка. Също така е важно и реда на връзките в html къде ще са, за google колкото по-нагоре е връзката толкова по-голям приоритет има. Затова примерно в даден сайт в кода текста на страницата може да е преди менюто, но визуално менюто да е от ляво, а текста от дясно.

Картинки

Всеки сайт трябва да има картинки, нали така? Обаче и тук има няколко особености. Като за начало е важно да се разграничат два вида картинки – едните са част от дизайна на сайта, а другите са част от неговото съдържание. Тези, които са част от дизайна са примерно фона на сайта, различни орнаменти и други подобни. А тези които са част от съдържанието са примерно снимки на продукт, ако това е каталожен сайт или снимка от новина, ако в сайта има новини.

При добре свършена работа <img> тага се ползва само за снимки от съдържанието. Като всяка една от тях трябва да има алтернативен текст в alt пропъртито. Докато другите картинки, част от дизайна, се слагат като фон чрез css (за него по-долу).

Много важна е оптимизацията на снимките. Като за начало е почти задължително вече всички картинки от дизайна да се обединяват в sprite. Т.е. да се обединят в една снимка и тя да се прилага. Вече, в зависимост от дизайна, може да се ползват и няколко sprite-а. Така от една страна е по-лесно да се организира сайта (колкото и странно да се струва това на пръв поглед). От друга, сайтът се зарежда по-бързо, защото трябва да се изтегли само една снимка, а не примерно 10-20.

Също така е важно всяка снимка (особено sprite)  да мине през нещо като SmushIt, за да и се намали размера, като и се изчистят ненужни мета данни.

Друга важна подробност е, че ако по дизайн някоя връзка, примерно от менюто, трябва да е картинка (например, защото там се ползва някой изчанчен шрифт с antialiasing), тя трябва да се сложи като фон. А текста, който е написан в нея, да се сложи и като title атрибут на връзката и да се сложи като текст в самата връзка (и да се скрие с css).

Cascading Style Sheets или за по-кратко css

Стиловете трябва да са отделени в css файл, за предпочитане само в един за по-лесно зареждане. Не е препоръчително ползването на <style> тага или inline стилове (style=”").

Ако се ползват стилове предназначени само за IE, не трябва да се ползват с хакове, а да са отделени във файлове според версията на IE чрез conditional comments. Например <!–[if lte IE 6]> <link rel=”stylesheet” type=”text/css” href=”stylesheets/IE6fix.css” /> <![endif]–> ще зареди файл важащ само за версии на IE до 6 включително. Стиловете трябва да се организират поне според структурата на html-а (header, content, sidebar, footer…) и по значението им (за текст, форми, заглавия…).

Не лоша идея е да се използва css reset.

Хубаво е пропъртитата да са подредени по азбучен ред и задължително да се ползват shorthand. Например padding-top: 5px; padding-right: 15px; padding-bottom: 12px; padding-left: 10px може да се изпише padding: 5px 15px 12px 10px. Имената на class и id трябва да са смислени, указващи частта от html, за която са предназначени (header, footer, nav_main, nav_categories). Ползването на дълги селектори като #header .menu .home li a се отразява зле на IE, а и по принцип е затормозяващо.

Ползването на нови селектори като box-shadow не е невъзможно, тъй като те имат поддръжка или алтернатива в повечето браузъри, включително IE, което намалява значително ползването на изображения. За намаляване и оптимизиране на css кода могат да се ползват css компресори, но такива, които не ползват нестандартни вредящи похвати, като да заменят strong с b. Освен това може да се приложи компресия gzip.

Приложна магия – JavaScript

JavaScript е един от най-неразбраните програмни езици в света. Поради различни причини, за които, ако трябва да пиша, ще ми трябват поне 1-2 поста още. Но въпреки това е един от любимите ми езици.  :)

Надявам се да не се налага да казвам че всичкия JavaScript трябва да е  unobtrusive.

За JavaScript си имам едно правило – всичко в сайта трябва да работи без javascript. Тук идеята е следната. Ако примерно имаме галерия със снимки – една голяма и малки тъмбнейли – при натискане на даден тъмбнейл се сменя голямата снимка. Обаче ако няма javascript, какво правим? Много просто – всяка снимка при натискане да се отваря в нов прозорец, за да може потребителя така или иначе да я види. А ако има javascript просто ще спрем отварянето и ще я покажем с хубав ефект.

Естествено, има неща, които без javascript не работят. Тук правя следното – просто ги крия, т.е. при зареждане на страницата са маркирани като невидими и след като се зареди javascript-а ги показвам.

Само тук искам да отбележа, че става дума за най-нормален сайт, който трябва да бъде достъпен отвсякъде и понякога без JavaScript просто няма накъде.

Браузъри

Yahoo имат един списък с браузъри – http://developer.yahoo.com/yui/articles/gbs/. Дефакто в A-Grade (и IE6) браузърите се налага сайта да работи. За IE6 може и да не работи толкова добре. Все пак, който ползва IE6 (и като цялата IE фамилия де), си е свикнал да не вижда хубави работи…

Скорост

Една от новите ми любими теми.

Като за начало има 2 инструмента, с които може да се замери скоростта на сайта по различни показатели (става дума само за html/css/js/assets часта) – YSlow и Google Page Speed.

Някои от най-важните неща са:

  • css най-отгоре и в един файл
  • javascript най-отдолу и в един файл
  • компресиране на всичко, което се праща от сървъра (тук има и малко работа по server-side)
  • expire headers
  • по-малко DOM  (html) елементи
  • по-прости css селектори

Една нова дума взе да се появява из различни презентации и постове при темата за Скорост и това е – Progressive HTML Rendering.

Идеята там е, че за потребителя страницата се “построява” пред очите му и така се създава усещането за скорост. За това много помага JavaScript да е възможно най-долу. Защото когато браузърът стигне <script> таг, забравя всичко останало. Затова много хора се насочват към това с малко javascript да зареждат останалия javascript, защото така не се блокира нишката на браузъра.

Заключение

И това е само една малка част от нещата, които се правят само по обвивката на един сайт. По принцип за всяка точка тук може да се пишат серии от постове. Да не говорим колко много развитие и промени има по темата.

Естествено. винаги може някой да си хване един dreamweaver и да си нареди 2-3 картинки и да каже, че има сайт. То и аз така съм почнал. Но в един момент това си е професия със своите тънкости и особености. Даже и името и е много яко -  Frontend Engineering.

п.п. Само да вметна, че аз се водя web developer  в Pixel Depo и основно работя по нещата, които стоят под frontend-a и JavaScript-a . :)

Apr 18

Наскоро (преди около месец де) излезе Prototype 1.6.1 RC1, основната цел на тази версия е съвместимост с ново излезлият IE8, и Element.Store ( за който бях писал преди време ).

Toчно за  Element.Store исках да драсна някои ред, защото изненадващо за този тип storage няма много материали в нет-а.

Основната идея на Element.Store е да може да се добавя информация (като обекти) към даден  елемент, без да се страхуваме от memory leaks. Това в Prototype е реализирано със следните методи (под $(element) имам предвид референция към prototype extend element):

  • $(element).getStorage() – директен достъп до Storage-а на даденият елемент,  той просто представлява един Hash в който се запазват всички обекти, които искаме да прикачим към елемента. Дефакто store/retrive се явяват shortcut-и на get/set методите на този Hash.
  • $(element).store(key, value) – записва информация в даденият елемент, ключът трябва да е string (или обект с метод toString), после когато искаме да си вземем информацията отново използваме ключа. Възможно е наведнъж да се запишат повече от една двойка ключ – стойност: $(element).store({ key1: ‘value1′, key2: ‘value2′ /* и така на татък */});
  • $(element).retrieve(key, defaultValue) – извлича информация от елемента по даден ключ, като ако не съществува такъв ключ се връща  defaultValue.
  • вероятно $(element).unsetStorage() (ticket | gist) – този метод все още го няма, но е много вероятно да се появи във финалната 1.6.1 версия, той трябва да премахва цялата информация за дадения обект от Storage-a му. Ако искате в момента да ползвате unsetStorage може да вземете моята примерна версия от тук.

Как обаче това би се ползвало в реалния свят ? Първо най-вероятно във финалната версия 1.6.1 цялата event система на Prototype ще използва Element.Store. А аз ще си позволя да дам един малък пример за това как и къде се ползва Element.Store:

Имаме примерно масив с футболисти на Арсенал, и на страницата имаме бутони, чийто id-та се образуват от ‘button_’ + номера на футболиста. Целта е при натискане на бутон за даден футболист да се показва името му с alert.

var footballers = [
	{number: 14, firstName: 'Theo',     lastName: 'Walcott'},
	{number: 12, firstName: 'Carlos',   lastName: 'Vela'},
	{number: 23, firstName: 'Andrei',   lastName: 'Arshavin'},
	{number: 4,  firstName: 'Francesc', lastName: 'Fabregas'},
	{number: 5,  firstName: 'Kolo',     lastName: 'Toure'},
	{number: 25, firstName: 'Emmanuel', lastName: 'Adebayor'}
];

Общо взето без имаме 4 възможни варианта:

// Вариант 1: използваме closure за достъп до футболиста
footballers.each(function(f){
	$('button_' + f.number).observe('click', function(){
		alert('#' + f.number + ' ' + f.firstName + ' ' . f.lastName);
	});
});

// Вариант 2: използване на скит closure
function show(f){
	alert('#' + f.number + ' ' + f.firstName + ' ' . f.lastName);
}

footballers.each(function(f){
	$('button_' + f.number).observe('click', show.curry(f));
});

// Вариант 3: използване на референция в самия обект
function show(){
	var f = this.footballer;
	alert('#' + f.number + ' ' + f.firstName + ' ' . f.lastName);
}

footballers.each(function(f){
	var button = $('button_' + f.number);
	button.footballer = f;
	button.observe('click', show);
});

// Вариант 4: използване на Element.Store
function show(f){
	var f = this.retrieve('footballer');
	alert('#' + f.number + ' ' + f.firstName + ' ' . f.lastName);
}

footballers.each(function(f){
	$('button_' + f.number).store('footballer', f).observe('click', show);
});

Всички варианти работят и правят едно и също (или поне така си мисля :) ), но всеки си има и своите особености. Така Вариант 1 изглежда добър вариант и също така е доста се използва именно този метод, но това което ме притеснява е че анонимната функция към бутона се пре-декларира за всеки бутон (въпреки, че това пак зависи от интерпретатора). Също така при по сложни скриптове може да се получат обърквания кое от къде идва и на къде отива, а и от самия код не е много “чист”. Във Вариант 2 се използва curry, което прави кода малко по-чист, но въпреки, че не е веднага видимо пак се използва closure ( в curry метода ) и отново има генериране на доста анонимни функции.  Вариант 3 променя още структурата кода, като се маха curry, и се добавя референция към футболиста в самия обект, така не се генерират множество функции, но вероятността да се появят memory leak-ове е много голяма ( а и самия код изглежда най-зле от всички варианти ). За мен лично вариант 4 е най-удачния (особено при по-големи проекти), сега към обекта пак се записва референция към дадения футболист, но този път се използва безопасния Element.store .

Надявам се с този малък пример да съм показал част от възможностите който предоставя новия Element.Store механизъм и защо е полезен той :)

В заключения ще дам за пример с CD3.Select класа ми, който се използва за правенето на custom html select контроли. Като просто като се напише new CD3.Select(selectElement), автоматично selectElement-а се замества с група от div/ul/li елементи и така се дава възможност на дизайнерите ни в Pixeldepo, да ги настройват както намерят за добре (всъщност в моите ControlDepo 3 Widgets имам още цял арсенал от компоненти, с подобни функции). Та преди в CD3.Select за капсулирането на връзката между options обектите и техните производни li елементи използвах closure, като след появата на Element.Store промених нещата. При което се забелязах че доста по-добре и глатко  работят самите контроли и много по-малко памет се използва :)

Element.Store използвам още за Event.deletege, който много се надявам да попадне в core-a на Prototype.js и за който в следващата седмица мисля да напиша един пост.

Mar 24

Преди време написах Учене на Javascript, но като всяко нещо и този списък остаря. Въпреки че самия JavaScript не се е променил много, все пак самия начин на работа с него се изменя непрекъснато.  Затова реших да спретна още един малък списък с ресурси за тези, които биха искали да се научат как се работи с JavaScript. Този е на малко по-високо ниво от предишния.

Започваме с добрия стар Douglas Crockford“Ajax Performance”

yuiblog връзка – http://yuiblog.com/blog/2008/12/23/video-crockford-performance/
video.yahoo.com връзка – http://video.yahoo.com/watch/4141759/11157560
m4v файл – http://us.dl1.yimg.com/download.yahoo.com/dl/ydn/yui/theater/crockford-performance.m4v
PPT презентация – http://yuiblog.com/assets/crockford-performance.zip

Малко от създателя на jQuery, John Resig и отново от yahoo – “The DOM Is a Mess”

yuiblog връзка – http://yuiblog.com/blog/2009/02/02/video-resig-2/
video.yahoo.com връзка – http://video.yahoo.com/watch/4403981/11812238
m4v файл – http://us.dl1.yimg.com/download.yahoo.com/dl/ydn/yui/theater/resig-dom.m4v
PPT презентация – http://www.slideshare.net/jeresig/the-dom-is-a-mess-yahoo

Също така, пак от John Resig – Learning Advanced JavaScript

Тъй като, предишните няколко ресурса са малко по-advanced, мисля да добавя и нещо малко по-просто Glenn Vanderburg  и The Power of Javascript. Доста полезни неща научих от нея, да не говорим, че и Glenn Vanderburg написа един страхотен  post – Why I still prefer Prototype to jQuery.

Колкото и да обичам video-то, се оказва че доста хора харесват все още добрата стара книга, затова ще добавя и Oliver SteelePractical Functional JavaScript: Code Samples.

С нотка на национална гордост препоръчвам и Object-Oriented JavaScript от Стоян Стефанов. Честно казано не съм я чел още, но от 2~3 познати съм чух, че е добро четиво. И възнамерявам в скоро време и аз да я прочета.

Ако някой знае още полезни ресурси за JavaScript да се чувства поканен да ги сподели.

Dec 01

Работейки по новата ми CMS система – Control Depo 3 ми се наложи да имам малко по advanced конфигурационен файл, така че за в бъдеще да ми е по-лесно да се наместват различните части на системата. Преди ползвах стандартното за едно PHP приложение – един файл config.php:

// start config
$_CONFIG = array();

// database
$_CONFIG['db_host'] = 'localhost';
$_CONFIG['db_name'] = 'project';
$_CONFIG['db_user'] = 'root';
$_CONFIG['db_pass'] = 'password';

// languages
$_CONFIG['default_language'] = 'bg';
$_CONFIG['laguages'] = array('bg' => 1, 'en' => 2, /* ... */);

// session
$_CONFIG['session_salt'] = 'SD23aeda';
$_CONFIG['session_expire'] = 4*60*60;
// ... и така много много реда код

После тази глобална променлива( $_CONFIG) когато ми трябва се вика със global и се ползва. Обаче от една страна че не е много красиво така написано, но от друга и това с global просто прави кода малко разхвърлян(на английски имат страхотна дума за това – messy) .

Това което ми трябваше основно е може да имам няколко enviroment конфигурационни файла подобно на Rails. Другото важно нещо беше да се побира в един екран, така че да не се налага да скролирам и да мога с един поглед да виждам всичко което ми трябва. И естествено да не е много сложно и да работи достатъчно бързо.

Първо погледнах Zend_Config, защо все пак са component-based-framework и може директно да видя как работи конфигурационната им система. И общо взето това което видях не ми хареса въобще. Много ми напомня на LEGOs, Play-Doh, and Programming от Jamis Buck, за която бях писал преди време(даже има я качена на видео в confreaks заедно с цялото rubyconf 2008).
От една страна колко пъти ще искам да ползвам XML и INI за конфигурация?! От една страна php си има array(), с която идеално може да се запишат всички неща който ни трябват и да са достатъчно четими. От друга самото четене на огромен xml/ini/yml/… файл отнема време и ресурси и са доста по-бавни от прост php код.

Добре че поне тук от Zend са сложили подразбиране да се ползва само php масив. Но от тук дойде 2рото ми учудване. Това а именно самата работа на имплементация на Zend_Config. Идеята е доста проста подава се масив, който се обгръща(wrap) от Zend_Config обект и след това се работи само със Zend_Config обект. Пример:

// Create the object-oriented wrapper upon the configuration data
$config = new Zend_Config(array(
'webhost'  => 'www.example.com',
'database' => array(
'adapter'   => 'pdo_mysql',
'params'    => array(
'host'      => 'db.example.com',
'username'  => 'dbuser',
'password'  => 'secret',
'dbname'    => 'mydatabase'
)
)
));

// Print a configuration datum (results in 'www.example.com')
echo $config->webhost;

// Use the configuration data to connect to the database
$db = Zend_Db::factory($config->database->adapter,
$config->database->params->toArray());

// Alternative usage: simply pass the Zend_Config object.
// The Zend_Db factory knows how to interpret it.
$db = Zend_Db::factory($config->database);

Идеята като цяло е много хубава, и при добро желание може човек да си направи основната част класовете му приемат Zend_Config обекти. Но има едно НО и то доста голямо. Защо ние е това? Единствения плюс който се сетих е че може да се направи immutable config обект. Но за сметка на това ще има доста излишен код и много памет за съхраниението на Zend_Config обекти и техните атрибути. А и колкото и да обичам __set/__get магиите тук ми се виждат напълно излишни защото както в примера:

$config->database->params->toArray()
// Това би представлявало нещо такова като backtrace
$config->__get('database')   // магически се търси атрибута 'database'
$config->_data['database']   // това което се връща тук пак е Zend_Config обект, който ще го нарека $object
$object->__get('params')     // пак същото за
$object->_data['params']     // друг $object
$object->toArray();          // автоматично всеки Zend_Config обект, който се стрещне му се вика Zend_Config::toArray()
$object->_data               // това за което ни трябва

Zend_Config си има и своите плюсове(immutable, секции, default стойности) и може за някое наистина “enterprise” приложение с много разчленена конфигурация (и много стабилен и мощен сървър :) ) да върши работа. Но не е за мен и аз си предпочитам “голите” array(). Все пак

No code is faster than no code

- някъде го чух това.

Така че какво реших ? Ами в предишния ми пост – PHP tips – include и return, описах метода за връщане на стойности от php файл и реших него да ползвам плюс малко правила и подрежанки. Сега имам следните файлове:

  • /config/config.php – тук ще е основната конфигурация
  • /config/enviroment/ – в тази папка ще се съдържат конфигурационните файлове за различните среди за работа, като стандартно имам 3 вида среди
  • /config/enviroment/development.php
  • /config/enviroment/test.php
  • /config/enviroment/production.php
  • /config/mailer.php – тук ще има конфигурация за mail системата, тя е изнесена в отделен файл, защото тук няма само да се връщат config данни, а и ще се вързва към smtp сървър (ако трябва), ще се настройват достъпи и други подобни неща. За разлика от другите конфигурационни файлове mailer.php се вика само когато се налага да се изпращат писма не по-рано.
  • /config/routes.php – това са настройките ми за различните пътища, подобно на Rails (пак)

Това са различните конфигурационни файлове (поне за сега).

return array(
'database'  => array(
'engine'    => 'mysql',
'host'      => 'localhost',
'name'      => 'project',
'user'      => 'root',
'pass'      => 'password'
),
'i18n'      => array(
'default'   => 'bg',
'languages' => array('bg' => 1, 'en' => 2, /* ... */)
),
'session'   => array(
'engine'    => 'cookie',
'salt'      => 'vW34Aaasa',
'expire'    => 4*60*60
), // на последния ред имам , защото при добавяне на нов ред да не ми се налага да я слагам
// а и SVN/Git ще го сметне като изтрит ред и после добавен ред
// ... още конфигурация ...
);
// /config/enviroment/development.php - примерно
return array(
'database'  => array(
'engine'    => 'mysql',
'host'      => 'localhost',
'user'      => 'root',
'pass'      => '',
'name'      => 'project_dev'
),
'smarty'    => array(
'compile_check'     => true,
'force_compile'     => false,
'debugging'         => false,
'caching'           => false,
'cache_lifetime'    => 0
),
'logging'           => true,
'display_errors'    => 1,
);

А в bootstrap-а имам просто това


// ENVIROMENT е просто константа в която казва в кой режим на работа е приложението
$_CONFIG = array_merge_recursive(include($_CFGDIR . '/config.php'), include($_CFGDIR . '/enviroments/' . ENVIROMENT . '.php'));

// ... код ...
// общо взето извличам всичко което ми трябва от $_CONFIG и го разпределям по обекти
// така че да не ми трябва повече $_CONFIG
// ... код ...

unset($_CONFIG /* заедно с още няколко вече не потребни ми променливи */);

Това решение ми се вижда най-елегантно, в моя случай. Може и да не може да се нарече “система” но е достатъчно надежно и ефективно да ми свърши работа. Все пак съм фен на Convention over configuration И ако ми се наложи да имам подобен на Zend_Config обект, с които да работя мисля да не променям основните неща, а просто този обект да използва ArrayAccess, но за това друг път, че сега май малко по-дълго стана от плануваното.

Надявам се някой да намери този пост полезен и ако съвети и идеи ще се радвам да ги чуя :) .

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 -ват всички ненужни глобални променливи, които не са нужни