Постове 1 - 5 от 12 с таг tips

Sep 17

Когато за пръв път видях Regular Expressions, ми се сториха нещо много яко и митично. И затова отделих доста време за да ги науча. Но в последствие се оказаха не толкова често използвано от мен средство. Най-често те са ми бързото решение, което в последствие се заменя с нещо по-добро. Но въпреки това ги използвам почти всеки ден.

Това за което ги ползвам е най-обикновен search & replace във файл (или проект). Това страшно много ми ускорява работата. Прост пример:

<a href="#">Link 1</a> |
<a href="#">Link 2</a> |
<a href="#">Link 3</a> |
<a href="#">Link 4</a> |
<a href="#">Link 5</a> |
<a href="#">Link 5</a> |
<a href="#">Link 6</a> |

И искаме да стане:

<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
<li><a href="#">Link 5</a></li>
<li><a href="#">Link 6</a></li>

За целта просто заменяме “(<a.*) \|” с “<li>$1</li>

Find: (<a.*) \|
Replace: <li>$1</li>

Като в някой редактори $1 съм го виждал като \1.

Друг “трик”, който ползвах когато писах PHP беше да преименувам даден метод (кой ме би по главата да не ползвам някое IDE е друга тема):

// от това
$foo->someMethod($foo1, "foo2", 'foo3');
$bar->someMethod($bar1, "bar2", 'bar3');
// трябва да се получи (забележете разменените аргументи)
$foo->otherAction("foo2", 'foo3', $foo1);
$bar->otherAction("bar2", 'bar3', $bar1);

За целта просто:

Find: ->someMethod\(([^,]*), ([^,]*), ([^,]*)\)
Replace: ->otherAction($2, $3, $1)

Като този пример може много да се деформира и много зависи от името на метода и дали няма други методи с това име във файла. И е добре винаги да имате тестове който да потвърждават че всичкия код който сте променили работи.

Много често ползвам  Regular Expressions по няколко пъти докато направя каквото ми трябва и го комбинирам със някои Textmate фунции (като ако сте под windows/linux препоръчвам E Texteditor). Естествено гледам да не прекалявам с това. Защото в един момент може и да не си заслужава мисленето на някакъв  сложен pattern. След като добрия стар ръчен find & replace би свършил работа.

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

Sep 11

Наскоро прочетох – “A couple of interesting UI techniques at Flickr” от Signal vs Noise. И особенно ми хареса номера с използването на unicode за визуализирането на стрелки. Като винаги ме е дразнело това че трябва да се прибягва до картинки за елементарни неща. Напоследък гледам да избягвам колкото мога използването на каквито и да е било картинки. Малко съм улеснен от това че там където ми се налга да пиша CSS не се налага да се подържа нито едно IE, така че мога да се възползвам от неща като border-radius / box-shadow / text-shadow / gradients и други.

Но да се върна на темата. По принцип за това за което yahoo са ползвали стрелката аз ползвам този номер – “iPhone back button“.  Обаче веднага след като видях това за unicode – ▲ (буквално след 5 мин) вече му намерих първото приложение. Заменяйки цели 3 картинки (normal / hover / active ) за един бутон.

Пример

Малко по късно използвах тази техника и за нещо за което си мисля от доста време. Когато се отвори папка стрелкичката с ефект да се завърти от една до друга позиция. Това сега става изключтелно лесно само чрез

.arrow {
  /* цвят и размерите на бутона, както и вертикално как да се разположи */
  font-size: 10px;
  color: #000;
  vertical-align: 2px;

  /* без подчертаване и outline */
  text-decoration: none;
  outline: none;

  /* това е важно зада може да трансформираме елемента */
  display: inline-block;

  /* завърти стрелката на 90 градуса */
  -webkit-transform: rotate(90deg);
  -moz-transform: rotate(90deg);
  -o-transform: rotate(90deg);

  /*
     това мисля че не се потдъжа от никой още,
     но много скоро мисля че стане стандарт
  */
  transform: rotate(90deg);

  /* анимирай всички промени по стрелката за 0.3 секунди */
  -webkit-transition: all 0.3s ease-out;
  -moz-transition: all 0.3s ease-out;
  -o-transition: all 0.3s ease-out;
}

/*
  тъй като не искам да пиша в кода си unicode синволи,
  е по-добре да се добави автоматично от css
*/
.arrow:after {
  content:  "▲";
}

/*
  когато е отворена стрелката тя се завърта на 180 градуса
*/
.arrow.opened {
  -webkit-transform: rotate(180deg);
  -moz-transform: rotate(180deg);
  -o-transform: rotate(180deg);
  transform: rotate(180deg);
}

За задействането на ефекта е необходимо само да се сложи или премахне клас “opened” от “.аrrow” елемент. Това ще работи при Chrome, Safari, Opera и Firefox. Като само при Firefox за ефектите ще се почака малко.

Ето и 2 примерни JavaScript кода за стрелките с Prototype и jQuery (който са “доста” подобни):

// Prototype
document.on('click', '.arrow', function(e, element){
  e.preventDefault();
  element.toggleClassName('opened');
});

// jQuery
$('a.arrow').live('click', function(e){
  e.preventDefault();
  $(this).toggleClass('opened');
});

Ето и примерен arrows.html

Заключение

Тук може да се сложат :hover , :focus, :active където примерно да се сменя цвета на бутона или сянката. И това ще става анимирано по презумция. И няма да се налага да се ходи до Photoshop, после да се генерират sprite-тове (между другото за тяхното генериране, мисля скоро ще open-source-на едно мое tool-че за работа с тях) и да се позиционират при най-малките промени.

Комбинациятата от unicode синволи и CSS3 е страшно мощна. И може да елиминира 2 от най-големите причини за “бавни” сайтове – ненужни картинки и javascript за ефекти.
Аз имам късмет че там където правя такива неща не се подържа IE и просто мога да кажа че си е цял рай. Особенно хубавото на css ефектите е че не трябва да се грижите за конфликти в анимцията и други дребни промени. Още повече в много Java Script код съм виждал излишно много писаници за ефекти, вместо да Java Script да се  грижи само за логиката.

Ако някой има идей и предложение по темата ще се радвам да ги чуя, тъй като това е доста нова територия.

п.п. Ако ви трябва и за IE да работи, аз лично бих направил отделен файл ie-only.css и там бих ползвал картинки (няма как) и не бих правил ефектите. Който ползва IE едно че не е свикнал да вижда добре работещи ефекти, две че и не заслужава такива неща.

Apr 04

Понеделник 29.03.2010

Ruby Quick Tip: Regular Expressions in Case Statements – Как може да се използват регулярни изрази в case конструкциите в Ruby.

Вторник 30.03.2010

Odd Ruby Methods – В Ruby могат да се предефинират операторите, в поста има няколко интересни примера и размисли.

Fred Wilson’s 10 Golden Principles of Successful Web Apps – Доста внимателно го слушах и съм напълно съгласен с човека. Много добре обяснява точка по точка, какво е задължително да се гледа когато се прави уеб приложение.

Сряда 31.03.2010

Hopping in the cloud – Поредната страхотна статия от Giant Robots Smashing Into Other Giant Robots. В статията се описва как са прехвърлили своето Hoptoad приложение в “облакът” на Engine Yard. Там се описват част от промените по цялостната архитектура на приложението, които са направили.

Четвъртък 01.04.2010

Много внимавам на първи Април, всяка година е пълно с “лъжливи мини”. Някои са доста доста лесни за разгадаване като – MooJo и Objective-Sprout. Докато примерно Github: Announcing SVN Support, два дни си мислех че е шега. А пък  “svn checkout http://svn.github.com/schacon/simplegit.git” си работи :)

Sproutcore and NodeJS are stars and comets – Две от любимите ми теми – NodeJs и SprouteCore. Какво просто да кажа :)

JavaScript Style – Chris Wanstrath доста нагледно е показал какъв е JavaScript стила в света.

Петък 02.04.2010

Microsoft Gets More Involved with jQuery – От една страна, това е добре за тези които работят с Microsoft технологии, че поне на готово ще имат една добра JavaScript библиотека. Но от друга от Microsoft, като се замисля, нищо хубаво не съм видял. И много се надявам да не я развалят много или да се опитат да я наложат на всички насила.

Federico Cargnelutti – Most Visited Posts of 2009 – Много харесвам този блог, според мен е един от най-стойностните PHP блогове. Това е малко закъснял списък с най-посещаваните постове за миналата година.

Mar 24

Днес прегледах поста – Image Resizing Made Easy with PHP от Nettuts+. Доста полезен пост, за начинаещи. Но както винаги имах няколко забележки относно от кода. Мислех да ги запиша като коментар, но той се оказа доста дълъг. Затова реших да го напиша като пост.

Като за начало ще е добре да видите началния код от тук ( променената от мен версия е тук). Така промените метод по метод.

openImage

private function openImage($file)
{
    // *** Get extension
    $extension = strtolower(strrchr($file, '.'));

    switch($extension)
    {
        case '.jpg':
        case '.jpeg':
            $img = @imagecreatefromjpeg($file);
            break;
        case '.gif':
            $img = @imagecreatefromgif($file);
            break;
        case '.png':
            $img = @imagecreatefrompng($file);
            break;
        default:
            $img = false;
            break;
    }
    return $img;
}
// става:
private function openImage($file){
    if (!is_file($file)){
        throw new Exception("File {$file} doesn't exists");
    }

    switch(pathinfo($file, PATHINFO_EXTENSION)){
        case 'jpg':
        case 'jpeg': return imagecreatefromjpeg($file);
        case 'gif':  return imagecreatefromgif($file);
        case 'png':  return imagecreatefrompng($file);
    }

    throw new Exception("Invalid image extension for {$file}. Acceptable image types are jpg,jpeg,gif,png");
}

Като за начало тук PHP има вградена функция pathinfo, която извикана с PATHINFO_EXTENSION константа като втори аргумент връща какво е разширението на файла.

Също така няма смисъл от дефинирането на променливите $extension  и $img защото реално се използват един път. $extension за switch-a, а пък $img само да се  return-е.

Също така @ е нещо което НЕ ТРЯБВА да се използва!  Освен че е страшно бавно (ако някой се интересува ще обясня вътрешно какви глупости прави и защо е толкова бавна операция).  От друга страна не е много редно да се крият грешките в програмите.

За това тук съм добавил два Exeption-а. По-принцип не обичам да ги ползвам, но в този случаи е наложително.  Трябва да се провери, първо дали файла който искаме да променяме съществува и второ дали от позволените типове.

resizeImage

public function resizeImage($newWidth, $newHeight, $option="auto")
{
    // *** Get optimal width and height - based on $option
    $optionArray = $this->getDimensions($newWidth, $newHeight, $option);

    $optimalWidth  = $optionArray['optimalWidth'];
    $optimalHeight = $optionArray['optimalHeight'];

    // *** Resample - create image canvas of x, y size
    $this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight);
    imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height);

    // *** if option is 'crop', then crop too
    if ($option == 'crop') {
        $this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight);
    }
}
// става:
public function resizeImage($newWidth, $newHeight, $option='auto'){
    list($width, $height) = $this->getDimensions($newWidth, $newHeight, $option);

    $this->imageResized = imagecreatetruecolor($width, $height);
    imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);

    if ($option == 'crop'){
        $this->crop($width, $height, $newWidth, $newHeight);
    }
}

List е доста подценявана PHP функционалност. Но за да може да се работи с нея промених и getDimensions да връща просто масив с два елемента на позиция 0 и 1. ( По-долу ще обясня за нея)

После промених имената на променливите $optimalWidth и $optimalHeight. От една страна са доста дълги и докато се пишат трябва да се натиска и shift заради W и H. От друга те се състоят от две думи, като едната е “optimal” която не ни трябва, даже и грешна. Защото се превеждат “оптимална ширина” и “оптимална височина” докато реално те са просто “ширина” и “височина” (т.е. $width и $height).

Описвам толкова дълго за тези променливи защото именуването на променливите е голяма част от красивия и разбираемия код.

getDimensions

private function getDimensions($newWidth, $newHeight, $option)
{

   switch ($option)
    {
        case 'exact':
            $optimalWidth = $newWidth;
            $optimalHeight= $newHeight;
            break;
        case 'portrait':
            $optimalWidth = $this->getSizeByFixedHeight($newHeight);
            $optimalHeight= $newHeight;
            break;
        case 'landscape':
            $optimalWidth = $newWidth;
            $optimalHeight= $this->getSizeByFixedWidth($newWidth);
            break;
        case 'auto':
            $optionArray = $this->getSizeByAuto($newWidth, $newHeight);
            $optimalWidth = $optionArray['optimalWidth'];
            $optimalHeight = $optionArray['optimalHeight'];
            break;
        case 'crop':
            $optionArray = $this->getOptimalCrop($newWidth, $newHeight);
            $optimalWidth = $optionArray['optimalWidth'];
            $optimalHeight = $optionArray['optimalHeight'];
            break;
    }
    return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getDimensions($width, $height, $option){
   switch ($option){
        case 'portrait':   return array($this->getSizeByFixedHeight($height),  $height);
        case 'landscape':  return array($width, $this->getSizeByFixedWidth($width));
        case 'auto':       return $this->getSizeByAuto($width, $height);
        case 'crop':       return $this->getOptimalCrop($width, $height);
        case 'exact':
        default:           return array($width, $height);
    }
}

Първо имената на променливите $newWidth и $newHeight на  $width и $height. Причините ги обясних по-горе за $optimalWidth и $optimalHeight.

Промених и формата на масива който се връща от getDimensions от асоциативен масив с ключове optimalWidth и optimalHeight на прост масив с индекси 0 и 1. Кода става доста по четим и по-горе в resizeImage метода може да се ползва List.

Премахват се и излишните променливи $optimalWidth и $optimalHeight, защото реално ни трябва просто масива с двата елемента. И от съкращаването на формата му се вижда че тези две променливи са още по-излишни.  Когато се намерят техните стойности веднага може да се върнат в масив, вместо да се чака да се стигне най-долу.

И накрая премествам ‘exact’ да бъде и default случая в switch.

getSizeByFixedHeight /  getSizeByFixedWidth

private function getSizeByFixedHeight($newHeight)
{
    $ratio = $this->width / $this->height;
    $newWidth = $newHeight * $ratio;
    return $newWidth;
}

private function getSizeByFixedWidth($newWidth)
{
    $ratio = $this->height / $this->width;
    $newHeight = $newWidth * $ratio;
    return $newHeight;
}
// става:
private function getSizeByFixedHeight($height){
    return ($this->width / $this->height) * $height;
}

private function getSizeByFixedWidth($width){
    return ($this->height / $this->width) * $width;
}

Много мразя дефинирането на излишни променливи. Защото докато се чете кода трябва да се помни какво има във съответната променлива.

Когато се погледне примерно getSizeByFixedHeight в оригиналната версия, кода се чете:

- Дефинирам $ratio който е ширината на снимката разделена на височината и
- Дефинирам нова височина, която е новата ширина умножена по $ratio ( мисля и се сещам какво има в $ratio )
- Връщам новата височина

Докато моята версия се чете просто:

- Разделям оригналата ширината на снимката на височината, полученото го умножавам по $width и връщам стойността.

getSizeByAuto

private function getSizeByAuto($newWidth, $newHeight)
{
    if ($this->height < $this->width)
    // *** Image to be resized is wider (landscape)
    {
        $optimalWidth = $newWidth;
        $optimalHeight= $this->getSizeByFixedWidth($newWidth);
    }
    elseif ($this->height > $this->width)
    // *** Image to be resized is taller (portrait)
    {
        $optimalWidth = $this->getSizeByFixedHeight($newHeight);
        $optimalHeight= $newHeight;
    }
    else
    // *** Image to be resizerd is a square
    {
        if ($newHeight < $newWidth) {
            $optimalWidth = $newWidth;
            $optimalHeight= $this->getSizeByFixedWidth($newWidth);
        } else if ($newHeight > $newWidth) {
            $optimalWidth = $this->getSizeByFixedHeight($newHeight);
            $optimalHeight= $newHeight;
        } else {
            // *** Sqaure being resized to a square
            $optimalWidth = $newWidth;
            $optimalHeight= $newHeight;
        }
    }

    return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getSizeByAuto($width, $height){
    if ($this->height < $this->width){
        return array($width, $this->getSizeByFixedWidth($width));
    }

    if ($this->height > $this->width){
        return array($this->getSizeByFixedHeight($height), $height);
    }

    if ($height < $width){
        return array($width, $this->getSizeByFixedWidth($width));
    }

    if ($height > $width){
        return array($this->getSizeByFixedHeight($height), $height);
    }

    return array($width, $height);
}

Колкото по надълбоко влизат вложените структури, толкова по-грозен е кода. Реално тук всяка ситуация може да се изнесе в отделен if и просто ако този if се изпълни се връща резултат веднага и така докато се стигне до края.

getOptimalCrop

private function getOptimalCrop($newWidth, $newHeight)
{

    $heightRatio = $this->height / $newHeight;
    $widthRatio  = $this->width /  $newWidth;

    if ($heightRatio < $widthRatio) {
        $optimalRatio = $heightRatio;
    } else {
        $optimalRatio = $widthRatio;
    }

    $optimalHeight = $this->height / $optimalRatio;
    $optimalWidth  = $this->width  / $optimalRatio;

    return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getOptimalCrop($width, $height){
    $ratio = min($this->height / $height, $this->width /  $width);
    return array(
        $this->width  / $ratio,
        $this->height / $ratio
    );
}

Мin е  друга малка PHP функция с чиято помощ този код се по изчиства. Защото реално на нас ни трябва минималната стойност от двете зависимости.

Тук пиша на два реда връщането на масива, отново заради четимостта.

saveImage и crop

private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight)
{
    // *** Find center - this will be used for the crop
    $cropStartX = ( $optimalWidth / 2) - ( $newWidth /2 );
    $cropStartY = ( $optimalHeight/ 2) - ( $newHeight/2 );

    $crop = $this->imageResized;
    //imagedestroy($this->imageResized);

    // *** Now crop from center to exact requested size
    $this->imageResized = imagecreatetruecolor($newWidth , $newHeight);
    imagecopyresampled($this->imageResized, $crop , 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight , $newWidth, $newHeight);
}
public function saveImage($savePath, $imageQuality="100")
{
    // *** Get extension
             $extension = strrchr($savePath, '.');
                $extension = strtolower($extension);

    switch($extension)
    {
        case '.jpg':
        case '.jpeg':
            if (imagetypes() & IMG_JPG) {
                imagejpeg($this->imageResized, $savePath, $imageQuality);
            }
            break;

        case '.gif':
            if (imagetypes() & IMG_GIF) {
                imagegif($this->imageResized, $savePath);
            }
            break;

        case '.png':
            // *** Scale quality from 0-100 to 0-9
            $scaleQuality = round(($imageQuality/100) * 9);

            // *** Invert quality setting as 0 is best, not 9
            $invertScaleQuality = 9 - $scaleQuality;

            if (imagetypes() & IMG_PNG) {
                 imagepng($this->imageResized, $savePath, $invertScaleQuality);
            }
            break;

        // ... etc

        default:
            // *** No extension - No save.
            break;
    }

    imagedestroy($this->imageResized);
}
// става:
private function crop($optimalWidth, $optimalHeight, $width, $height){
    $x = ( $optimalWidth  / 2) - ( $width  /2 );
    $y = ( $optimalHeight / 2) - ( $height /2 );

    $crop = $this->imageResized;

    $this->imageResized = imagecreatetruecolor($width , $height);
    imagecopyresampled($this->imageResized, $crop, 0, 0, $x, $y, $width, $height , $width, $height);
}

public function saveImage($savePath, $imageQuality="100"){
      switch(pathinfo($savePath, PATHINFO_EXTENSION)){
        case 'jpg':
        case 'jpeg':
            if (imagetypes() & IMG_JPG){
                imagejpeg($this->imageResized, $savePath, $imageQuality);
            }
            break;

        case 'gif':
            if (imagetypes() & IMG_GIF){
                imagegif($this->imageResized, $savePath);
            }
            break;

        case 'png':
            if (imagetypes() & IMG_PNG){
                // Scale quality from 0-100 to 0-9
                // Invert quality setting as 0 is best, not 9
                $invertScaleQuality = 9 - round(($imageQuality/100) * 9);
                imagepng($this->imageResized, $savePath, $invertScaleQuality);
            }
            break;
    }

    imagedestroy($this->imageResized);
}

Промените по saveImage и crop са просто стилистични, така че няма да се спирам много подробно на тях.

Финални думи

Като започнах да чистя кода си обещах, че няма да променям имената на методите. Но ако питате мен от публичните методи – resizeImage и saveImage бих махнал “Image” и да станат само resize и save. Защото класа най-често би се използвал с променливи кръстени $resizer, $resize, $image и подобни и затова rеsize и save биха достатъчно ясно.

Като цяло урока от Nettuts+ е много добър. Въпроса че според мен в уроците за начинаещи, които главно ползват Nettuts+, трябва да има максимално добър код. Без излишен “шум”.

Надявам се този малък анализ да е бил полезен на някого. Приемам всякакви критики и съвети, под формата на коментари :)

Mar 14

Наскоро се наложи да поработя по един доста стар PHP проект, по който не бях работил от години. И там видях нещо, което ведна опреличих като code smell (Всъщност видях много такива неща, но само на това ще обърна внимание).

$cart = CartManager::getCurrentCart();
$cart->setClientInfo('atype',    $user['account_type']);
$cart->setClientInfo('fname',    $user['fname']);
$cart->setClientInfo('lname',    $user['lname']);
$cart->setClientInfo('street',   $user['street']);
$cart->setClientInfo('postnum',  $user['postcode']);
$cart->setClientInfo('epost',    $user['mail']);
$cart->setClientInfo('city',     $user['city']);
$cart->setClientInfo('company',  $user['company']);
$cart->setClientInfo('orgnum',   $user['orgnum']);
$cart->setClientInfo('phone',    $user['phone']);
$cart->setClientInfo('country',  $user['country']);

Което ако идвате от  Java света може и да ви изглежда нормално, но мен ме дразни.

На първо време направих метода setClientInfo да връща $this зада може да използвам прост method chaining. И кода стана така:

CartManager::getCurrentCart()
    ->setClientInfo('atype',    $user['account_type'])
    ->setClientInfo('fname',    $user['fname'])
    ->setClientInfo('lname',    $user['lname'])
    ->setClientInfo('street',   $user['street'])
    ->setClientInfo('postnum',  $user['postcode'])
    ->setClientInfo('epost',    $user['mail'])
    ->setClientInfo('city',     $user['city'])
    ->setClientInfo('company',  $user['company'])
    ->setClientInfo('orgnum',   $user['orgnum'])
    ->setClientInfo('phone',    $user['phone'])
    ->setClientInfo('country',  $user['country']);

Малко по-добре, но пак ми изглеждаше не естествено. Затова просто добавих възможността да може да се предава масив като аргумент. И така стана доста добре:

CartManager::getCurrentCart()->setClientInfo(array(
    'atype',    => $user['account_type'],
    'fname',    => $user['fname'],
    'lname',    => $user['lname'],
    'street',   => $user['street'],
    'postnum',  => $user['postcode'],
    'epost',    => $user['mail'],
    'city',     => $user['city'],
    'company',  => $user['company'],
    'orgnum',   => $user['orgnum'],
    'phone',    => $user['phone'],
    'country',  => $user['country']
));

За нещастие в този проект, нямаше как да направя кода стане още по-чист, без да се налага пренаписване на голяма част от проекта:

CartManager::getCurrentCart()->setClient($user);