Nette: zmenšení průhledného PNG, zachování průhlednosti

Při zmenšování průhledného PNG obrázku v Nette jsem měl problém se zachováním průhlednosti. Místo průhlednosti byla černá barva. Nakonec jsem problém vyřešil takto:

$photo->alphaBlending(true);
$photo->resize( 1000, 1000, Nette\Utils\Image::SHRINK_ONLY );
$photo->alphaBlending(false);
$photo->saveAlpha(true);
$photo->save( ... );

Kód resize je obalen alphaBlending a saveAlpha

CoffeeScript + Node.js => Hello world

Poslední dobou ujíždím na Node.js. Neskutečně rychlý nástroj! K tomu se pokouším naučit CoffeeScript. Blog dost často používám jako „tahák“ co jsem jak dělal. Pojďme si ukázat jak na první Hello World.

CoffeeScript můžeme nainstalovat globálně (bude přístupný v celém OS):

npm install -g coffee-script

Inicilizujeme Node.js projekt – vyplníme požadované údaje, čímž se vytvoří soubor package.json:

npm init

Node přidáme jako závislost do projektu (packages.json):

npm install coffee-script --save

server.coffee:

#toto je komentar

http = require 'http'
http.createServer (req, res) ->
        res.writeHead 200
        res.end 'Hello, World!'
        .listen 5000
console.log "listening on port 5000"

Spustíme coffee script v Node.js:

coffee server.coffee

V prohlížeči na adrese http://localhost:5000/ vidíme výsledek

Velice zajímavý mi přijde modul express, což je minimalistický a rychlý framework pro pohodlnější generování stránek:

npm install express --save
express = require('express')

app = express()
port = 5000

app.get '/', (req, res) ->
  res.send 'Hello world!'

app.get '/contact', (req, res) ->
  res.send 'contact'

app.listen port, () ->
  console.log "listening on port #{port}"

Spustíme příkazem

coffee server.coffee

a o správné funkčnosti se opět přesvědčíme na portu 5000

Node.js crawling data

Pokud někdy budete chtít získávat obsah webových stránek, doporučuji kvůli rychlosti použít Node.js a modul nazvaný jsdom. Tento modul analyzuje webovou stránku ze které získá DOM, který můžete parsovat přes jQuery.

Před nainstalováním jsdom je nutné nainstalovat Python (verze 2.x) Při instalaci je nutné zaškrtnou volbu Python do PATH (App python.exe to Path).

Nainstalujeme jsdom:

npm install jsdom --save

example.js

// Count all of the links from the Node.js build page
var jsdom = require("jsdom");

jsdom.env(
  "http://nodejs.org/dist/",
  ["http://code.jquery.com/jquery.js"],
  function (errors, window) {
    console.log("there have been", window.$("a").length, "nodejs releases!");
  }
);

spustíme příkazem:

C:\node>node example.js
there have been 245 nodejs releases!

 

Jak nainstalovat Grunt pod Windows

Pojďme si ukázat nástroj, který za mne dělá špinavou práci. Všechny procesy které děláte stále ručně do kola se většinou dají zautomatizovat. Já je nechávám na Gruntu aby je udělal za mne. Stroj nedělá chyby a je rychlý.

Dnes si ukážeme úvod do Gruntu. S Gruntem spolupracuje další skvělý nástroj – Bower. Ale o tom až někdy příště.

Pro zprovoznění programu Grunt je nutné nainstalovat node.js

Z webu stáhneme instalaci (tlačítko INSTALL) a nainstalujeme. Instalaci stačí odklikat. Po dokončení instalace pro jistotu restartujeme PC.

Přesvědčíme se že máme správně nainstalovaný node.js. Spustíme příkazovou řádku ( Start -> Příslušenství -> Příkazový řádek ). Do příkazové řádky zadáme příkaz node a potvrdíme klávesou ENTER:

node-test-run

Součástí každého projektu by měl být soubor package.json. Vytvoříme ho příkazem:

npm init

package.json obsahuje základní informace o projektu ( autor, verze, verzi Node, závislé balíčky a jejich verze, …) je to něco jako composer.json pro php. Pokud si projekt s tímto souborem stáhnete, jednoduše nainstalujete všechny potřebné balíčky příkazem:

npm install

Instalace Grunt

npm install -g grunt
npm install -g grunt-cli
npm install grunt --save-dev       //musim provest i tento prikaz

Parametr -g říká, že Grunt instalujeme globálně.

Úspěšné nainstalování Gruntu prověříme příkazem:

grunt

node-test-run-grunt

Z hlášky v konzoli je vidět že nemáme soubor Gruntfile.js pojďme si ho vytvořit (ten musíme vytvořit ručně):

module.exports = function(grunt) {
   grunt.initConfig({
      section: {
         foo: [1, 2, 3],
         bar: 'hello world',
         baz: false
      }
   })
   grunt.task.registerMultiTask('section', 'run section.', function() {
      grunt.log.writeln(this.target + ': ' + this.data);
   });
   grunt.task.registerMultiTask('section', 'run section.', function() {
      grunt.log.writeln(this.target + ': ' + this.data);
   });

   grunt.registerTask('only-baz', ['section:baz']);
}

Vytvořili jsme 2 tasky:

  • section – multiTast vykoná všechny tasky v sekci section
    node-test-run-grunt-run
  • only-baz – vykoná pouze 1 task ze sekce section ( section -> baz )
    node-test-run-grunt-run-1task

Neužitečný příklad výstupu, ovšem nutný pro pochopení fungování Gruntu. Příště si ukážeme zajímavější použití v praxi 🙂

Nette: handle (signál)

Předpokládejme že máme TODO list ve kterém budeme chtít vybraný úkol nastavit jako splněný.

Na takový úkon je vhodný signál:

public function handleMarkDone( $taskId )
{
    $this->table->where(array('id' => $taskId))->update(array('done' => 1));
    $this->redirect('this');
}

Signál v šabloně zavoláme jednoduše:

<a n:href="markDone! $task->id">hotovo</a>

 

Grunt – task pro automatický release nové verze

Dělám na několika doplňcích pro WordPress. Kámen úrazu je verzování. S vydáním nové verze jsem musel přepisovat verzi ve zdrojovém kódu, vytvořit nový tag v Gitu a tuto změnu commitnout a pushnout. Ale protože jsem lajdák vždy jsem na něco zapoměl…

Při vydání nové verze dělám:

  • nastavit novou verzi ve zdrojových souborech ( Version: 0.0.1 => Version: 0.0.2 )
  • coomitnout tuto změnu
  • vytvořit nový tag ( 0.0.2 )
  • pushnout tyto změny

To jsem dělal ručně do doby než jsem poznal Grunt. Nyní mám tento proces automatizovaný a tuto nudnou opičí práci za mne udělá Grunt.

Pojďme si ukázat jak. Předpokládejme nainstalovaný Grunt (o instalaci Gruntu zase někdy jindy).

Vytvoříme soubor package.json:

npm init

Nainstalujeme grunt-version:

npm install grunt-version --save-dev

Vytvoříme soubor Gruntfile.js:

module.exports = function(grunt) {
   grunt.loadNpmTasks('grunt-version');
 
   grunt.initConfig({
      version: {
         php: {
            options: {
               prefix: '\Version:\\s+'
            },
            src: [ 'my-file.php' ]
         }
      }
   });

   // Release task
   grunt.registerTask( 'release', [ 'version' ]); 
};

Příkazem grunt release nastavíme v souboru my-file.php novou verzi uvedenou v package.json:

Version:           0.0.1 se změní na verzi zadanou v souboru package.json

To je solidní základ 🙂 Ale mne to nestačí. Verzi kterou chci vydat musím zapsat do souboru package.json a příliš práce to za mne neudělá…

Dělám různé releasy:

  • patch (1.2.X)
  • minor (1.X.0)
  • major (X.0.0)

Pojďmě si napsat script který bude automaticky zvyšovat verzi v souboru package.json v závislosti na vydávané verzi. Vužijeme modul grunt-update-json

npm install grunt-update-json --save-dev

Použijeme tento kód:

module.exports = function(grunt) {

   grunt.loadNpmTasks('grunt-version');
	
   grunt.initConfig({
      pkg: grunt.file.readJSON( 'package.json' ),
         version: {
            php: {
               options: {
                  prefix: '\Version:\\s+'
               },
                  src: [ 'my-file.php' ]
               }
            }
         });
         // Release task		
         grunt.registerTask('release:patch', 'Release patch version', function( target ) {
            release( "patch" );
         });

         grunt.registerTask('release:minor', 'Release minor version', function( target ) {
            release( "minor" );
         });

         grunt.registerTask('release:major', 'Release major version', function( target ) {
            release( "major" );
         });

         function release( type )
         {
            var version = grunt.config.data.pkg.version;
            var aVersion = version.split(".");
            if( type == "patch" )
            {
               aVersion[ 2 ] = ( 1 * aVersion[ 2 ] ) + 1;
            }
            else if( type == "minor" )
            {
               aVersion[ 2 ] = 0;
               aVersion[ 1 ] = ( 1 * aVersion[ 1 ] ) + 1;
            }
               else if( type == "major" )
            {
               aVersion[ 2 ] = 0;
               aVersion[ 1 ] = 0;
               aVersion[ 0 ] = ( 1 * aVersion[ 0 ] ) + 1;
            }
            var newVersion = aVersion.join(".");
            grunt.log.writeln('Release ' + type + ' version: ' + version + ' => ' + newVersion );

            //save new version to package.json
            grunt.config.data.pkg.version = newVersion;		
            grunt.file.write( './package.json', JSON.stringify( grunt.config.data.pkg, null, '  ') + '\n');
		
            //change version in source code
            grunt.task.run(['version']);
		
            //@TODO commit
            //@TODO create TAG
            //@TODO push
         }
};

To už je lepší. Můžeme použít následující příkaz:

  • grunt release:patch
  • grunt release:minor
  • grunt release:major

K dokonalosti chybí pouze commit, vytvořit tag s novou verzí a push. K tomu využijeme grunt-git

npm install grunt-git --save-dev

Do souboru package.json přidáme následující kód:

gitcommit: {
   version: {
      options: {
         message: 'New version: <%= pkg.version %>'
      },
      files: {
         // Specify the files you want to commit
         src: ['my-file.php', 'package.json']
      }
   }
},
gittag: {
   version: {
      options: {
         tag: '<%= pkg.version %>',
         message: 'Tagging version <%= pkg.version %>'
      }
   }
},
gitpush: {
   version: {},
   tag: {
      options: {
         tags: true
      }
   }
},

Celý kód nakonec vypadá takto:

module.exports = function(grunt) {

   grunt.loadNpmTasks('grunt-version');
   grunt.loadNpmTasks('grunt-git');
	
   grunt.initConfig({
      pkg: grunt.file.readJSON( 'package.json' ),
         version: {
            php: {
               options: {
                  prefix: '\Version:\\s+'
               },
               src: [ 'my-file.php' ]
            }
         },
         gitcommit: {
            version: {
               options: {
                  message: 'New version: <%= pkg.version %>'
               },
               files: {
                  src: ['my-file.php', 'package.json']
               }
            }
         },
         gittag: {
            version: {
               options: {
                  tag: '<%= pkg.version %>',
                  message: 'Tagging version <%= pkg.version %>'
               }
            }
         },
         gitpush: {
            version: {},
            tag: {
               options: {
                  tags: true
               }
            }
         }		
      });

      grunt.registerTask('release:patch', 'Release patch version', function( target ) {
         release( "patch" );
      });

      grunt.registerTask('release:minor', 'Release minor version', function( target ) {
         release( "minor" );
      });

      grunt.registerTask('release:major', 'Release major version', function( target ) {
         release( "major" );
      });

      function release( type )
      {
         var version = grunt.config.data.pkg.version;
         var aVersion = version.split(".");
         if( type == "patch" )
         {
            aVersion[ 2 ] = ( 1 * aVersion[ 2 ] ) + 1;
         }
         else if( type == "minor" )
         {
            aVersion[ 2 ] = 0;
            aVersion[ 1 ] = ( 1 * aVersion[ 1 ] ) + 1;
         }
         else if( type == "major" )
         {
            aVersion[ 2 ] = 0;
            aVersion[ 1 ] = 0;
            aVersion[ 0 ] = ( 1 * aVersion[ 0 ] ) + 1;
         }
         var newVersion = aVersion.join(".");
         grunt.log.writeln('Release ' + type + ' version: ' + version + ' => ' + newVersion );

         //save new version to package.json
         grunt.config.data.pkg.version = newVersion;		
         grunt.file.write( './package.json', JSON.stringify( grunt.config.data.pkg, null, '  ') + '\n');
		
         //change version in source code
         grunt.task.run(['version']);
		
         //git commit
         grunt.task.run(['gitcommit:version']);
         //git create TAG
         grunt.task.run(['gittag:version']);
         //git push
         grunt.task.run(['gitpush']);
      }
};

 

 

Nette + Texy

Pokud si zájemce o web přeje základní formátování textů, vetšinou nejprve sáhnu po Texy. Nemám rád weby které vypadají jako omalovánky (ano, tomu kdo dá tu práci aby každé písmenko ve slově mělo jinou barvu se to moc líbí ale spoustu návštěvníků to odradí). Texy je jednoduchý nástroj kterým můžete bez odborných znalostí psát HTML kód. Příklad si ukážeme na jednoduchém formuláři, jehož odesláním naformátujeme zadaný text přes Texy.

 

Pojďme si ukázat jak zprovoznit  Nette společně s Texy!

Ukázka je na poslední verzi Nette (2.2.6) a poslední verzi Texy (2.6).

Instalace Nette

Stáhneme Nette sandbox (přes composer):

composer create-project nette/sandbox nette-texy

příkazem se vytvoří složka nette-texy do které se nahraje Nette + sandbox. Document root pro virtualhost nasměrujeme do nette-texy/www/

Instalace Texy

nainstalujeme samozřejmě přes composer :

composer require dg/texy:~2.0

 

Zprovoznění Texy

Uděláme si pořádek na hřišti. Nahradíme obsah šablony pro titulní stránku: app/templates/Homepage/default.latte:

{block content}
   {ifset $text}
      {$text|texy|noescape}
   {/ifset}
   {control texyForm}
{/block}

 Vytvoření formuláře

app/presenters/HomepagePresenter.php:

<?php

namespace App\Presenters;

use Nette;

class HomepagePresenter extends BasePresenter
{

    protected function createComponentTexyForm()
    {
        $form = new Nette\Application\UI\Form;
        $form->addTextarea('text', 'Text:')
                ->setAttribute('cols', 80)
                ->setAttribute('rows', 10);
        $form->addSubmit('send', 'Send');

        $form->onSuccess[] = $this->texyFormSucceeded;
        return $form;
    }


    public function texyFormSucceeded($form, $values)
    {
        $this->template->text = $values->text;
    } 
}

Máme formulář který při odeslání odešle do šablony (proměnná $text ) zadaný text.

Zapnutí Texy

Nejprve je nutné přidat Texy do neon.config aby o Nette o Texy vědělo:

app/config/config.neon:

 

services:
	- App\Model\UserManager
	- App\RouterFactory
	router: @App\RouterFactory::createRouter
	texy: Texy

 

V souboru app/presenters/BasePresenter.php je nutné vytvořit helper, který bude fomárovat text přes Texy.

<?php

namespace App\Presenters;

use Nette;
use TexyConfigurator;

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    /** @var \Texy */
    private $texy;

    public function injectTexy( \Texy $texy )
    {
        $this->texy = $texy;

        //$this->texy->allowed['heading/underlined'] = FALSE;// podtržené titulky
        //$this->texy->allowed['heading/surrounded'] = FALSE;// ohraničené titulky

        //TexyConfigurator::safeMode( $this->texy ); //nastaveni pro diskuse, ktere nepovoli vlozeni kompromitovaneho kodu
        //TexyConfigurator::disableLinks( $this->texy );
        //TexyConfigurator::disableImages( $this->texy );
    }

    protected function createTemplate( $class = NULL )
    {
        $template = parent::createTemplate( $class );
        $template->registerHelper( 'texy', callback( $this->texy, 'process' ) );
        return $template;
    }

}

V nové verzi Nette (>=2.4) se helper (filtr) registruje takto, metodu createTemplate můžete úplně vynechat):

    protected function beforeRender()
    {
        parent::beforeRender();

        $this->template->addFilter('texy', function ($text) {
            return $this->texy->process($text);
        });

    }

Nejlepší cesta

Kvůli bezpečnosti, doporučuji tento způsob použití, ve kterém filtr vrací Nette\Utils\Html, který nemusíme nonescapovat. Registrujeme filter klasicky v beforeRender:

    protected function beforeRender()
    {
        parent::beforeRender();

        $this->template->addFilter('texy', function ($text) {
            //return $this->texy->process($text);
            return Nette\Utils\Html::el()->setHtml($texy->process($text));
        });

    }

A pak již pouze v šabloně vypíšeme:

{$text|texy}

 

Integrace Bitbucket + Pivotal tracker

Zalíbíl se mi verzovací hosting Bitbucket. Oproti GitHubu nabízí neomezené množství privátních repozitářů.

Poohlížím se po trackovacím nástroji a zalíbíl se mi Pivotal tracket který je možné integrovat s Bitbucketem. Jak na to?

 

Pivotal Tracker

  • přejděte do sekce: Profile -> API token

Bitbucket

  • zvolte repozitář který si přejete propojit s Pivotal trackerem
  • Settings -> Integrations -> Hooks
  • Vyberte hook „Pivotal Tracker“ a zadejte API token z Pivotal trackeru

Nyní když v commit zprávě uvedete id tiketu z Pivotal trackeru, bude commit a task propojen. V Pivotal trackeru u tasku uvitíte jednotivé commity.

Commit se s taskem propojí identifikátorem: [#123456] kde 123456 je ID Pivotal tasku

Znáte lepší trackovací nástroj, který je zdarma?

Nette – jak vytvořit REST API – json

Co to je REST

REST neboli Representational state transfer je způsob jak jednoduše číst (GET), editovat (PUT), mazat (DELETE) a vytvářet (POST) obsah na serveru pomocí HTTP volání.

REST je vhodný pro jednotný a snadný přístup ke zdrojům. Na rozdíl od SOAP či XML-RPC je REST orientován datově, nikoli procedurálně. Všechny REST zdroje mají jednoznačný identifikátor (URL)

Jak na to v Nette?

Zapomeňte na nějaké složité generování v šabloně. Dokonce nebudete potřebovat ani funkci json-encode. V Nette uděláte JSON výstup velice jednoduše.

Ukázka vygenerování JSONu:

public function actionJson()
{
   $a = array( "date" => "20.8.2014 22:34", "temp_in" => "24.2°C", "temp_out" => "14.9°C" );

   $this->sendResponse( new Nette\Application\Responses\JsonResponse( $a ) );
}

Důležité je, že response je možné vygenerovat pouze metodou sendResponse které předhodíte response

TIP: pokud budete chtít posílat i kódování, provedete to následujícím způsobem:

$this->sendResponse( new Nette\Application\Responses\JsonResponse( $a, "application/json;charset=utf-8" ) );