Montag, 15. November 2010

How to add a "Save and List" Action Button to Symfony Admin Generator Form.

To improve the workflow sometimes it may be better to redirect the user back to the list after the Save-action. This tutorial shows how to implement this in Symfony 1.3 and 1.4

First copy the "processForm"-function from the action.class.php in your cache-directory ( /cache/dev/modules/auto[Modulename]/actions/actions.class.php ) to your action.class.php and change the lines:

if ($request->hasParameter('_save_and_add'))
{
$this->getUser()->setFlash('notice', $notice.' You can add another one below.');

$this->redirect('@[Modulename]_new');
}
else
{
$this->getUser()->setFlash('notice', $notice);

$this->redirect(array('sf_route' => '[Modulename]_edit', 'sf_subject' => $[Modulename]));
}


to:


if ($request->hasParameter('_save_and_add'))
{
$this->getUser()->setFlash('notice', $notice.' You can add another one below.');

$this->redirect('@[Modulename]_new');
}
else if ($request->hasParameter('_save_and_list'))
{
$this->getUser()->setFlash('notice', $notice);

$this->redirect('@[Modulename]');
}
else
{
$this->getUser()->setFlash('notice', $notice);

$this->redirect(array('sf_route' => '[Modulename]_edit', 'sf_subject' => $[Modulename]));
}


Open the file /apps/[Appname]/modules/[Modulename]/lib/[Modulename]GeneratorHelper.class.php and add the function:


public function linkToSaveAndList($object, $params)
{

return '
  • ';
    }


    In /apps/[Appname]/modules/[Modulename]/config/generator.yml:

    ...
    config:
    ...
    form:
    actions:
    save_and_list: ~
    ...


    Now there is a new button "Save and list" at the bottom of the Edit-form that will redirect the user to the list after saving the form. Maybe it would be useful to change the generator-classes to auto generate this button for every module.

    Dienstag, 14. September 2010

    Fun with HTML5 Canvas Effects

    During my work on the new AIE Ajax Image Editor, I have done some experiments with the new HTML5 features, especially the new Canvas tag. This script is basically a new implementation in JavaScript of a old Director-Movie which I have made about 10 years ago.


    Simply move over the image to distort it. Click to switch effect.


    The script is very simple: first it gets the content of a image ("getImageData"-function).

    window.onload = function() {
    canvas = document.getElementById("area");
    context = canvas.getContext("2d");
    image = document.getElementById("canvasSource");
    context.drawImage(image, 0, 0);
    imgdo = context.getImageData(0, 0, imgsizex, imgsizey);
    document.getElementById("area").onmousemove=draw;
    document.getElementById("area").onclick=function(){ mode++; if(mode>2) mode=0; };
    };

    Then it distorts the image using some trigonometric functions:

    function draw(){
    var imgd = context.getImageData(0, 0, imgsizex, imgsizey);

    var opix = imgdo.data;
    var npix = imgd.data;

    var mpos={x:100,y:100};
    var area=document.getElementById("area");
    mpos.x=Mouse.x-area.offsetLeft;
    mpos.y=Mouse.y-area.offsetTop;
    for (iy=0; iy for (ix=0; ix var a=(ix - mpos.x);
    var b=(iy - mpos.y);
    var c=Math.abs(Math.sqrt(Math.abs(a*a)+Math.abs(b*b)));
    var w=getwinkel(a,b);
    if (mode==0) {
    var wd=90-(c);
    if (wd<0) wd=0;
    if (wd>90) wd=90;
    var neww=grad2rad(w+wd/1);
    var newa=Math.sin(neww)*c;
    var newb=Math.cos(neww)*c;
    var z=(iy*imgsizex+ix)*4;
    var nz=Math.round((Math.round(mpos.y+newb)*imgsizex+Math.round(mpos.x+newa)))*4;
    }else if (mode==1){
    var wd=90-(c);
    if (wd<0) wd=0;
    if (wd>90) wd=90;
    var neww=grad2rad(w);
    var newc=c+wd/10;
    var newa=Math.sin(neww)*newc;
    var newb=Math.cos(neww)*newc;
    var z=(iy*imgsizex+ix)*4;
    var nz=Math.round((Math.round(mpos.y+newb)*imgsizex+Math.round(mpos.x+newa)))*4;
    }else{
    var wd=(c);
    if (wd<0) wd=0;
    if (wd>90) wd=90;
    var neww=grad2rad(w);
    var newc=c+wd/10;
    var newa=Math.sin(neww)*newc;
    var newb=Math.cos(neww)*newc;
    var z=(iy*imgsizex+ix)*4;
    var nz=Math.round((Math.round(mpos.y+newb)*imgsizex+Math.round(mpos.x+newa)))*4;
    }
    npix[z ] = opix[nz ] || 0; // red
    npix[z+1] = opix[nz+1] || 0; // green
    npix[z+2] = opix[nz+2] || 0; // blue

    }
    }
    var a=10;
    var b=10;
    var c=Math.sqrt(a*a+b*b);
    context.putImageData(imgd, 0, 0);
    }

    It basically calculates the distance and the angle to the mouse position of every pixel. Then it adds a percentage of the distance to the angle and recalculates the new pixel-position. Then it copies the RGB-value of the pixel at the calculated position in the original image to the pixel in the new image.

    I have made those functions to convert between Rad and grad and to calculate the angle:

    function rad2grad(rad){
    return rad*180/Math.PI;
    }
    function grad2rad(grad){
    return grad*Math.PI/180;
    }
    function getwinkel(a,b){
    var c=Math.abs(Math.sqrt(Math.abs(a*a)+Math.abs(b*b)));
    var nw=rad2grad(Math.asin(a/c));
    if (a<0 && b<0){
    nw= -nw-180;
    }else if (b<0){
    nw= 180-nw;
    }
    return nw;
    }


    ...and the complete Code:






    Fun with HTML5 Canvas Effects



    Fun with HTML5 Canvas Effects - Example

    Author: 2010 Julian Stricker - www.julianstricker.com

    Canvas Source










    Donnerstag, 24. Juni 2010

    Validate size of a uploaded Image in Symfony 1.3/1.4

    The sfValidatorFile has no options to check the size of a image in pixel. For that I have written a simple validator that extends the sfValidatorFile-class.

    Create the file jsValidatorImage.php in your /lib directory:


    class jsValidatorImage extends sfValidatorFile
    {
    protected function configure($options = array(), $messages = array())
    {
    parent::configure($options, $messages);

    $this->addOption('minx', false);
    $this->addOption('miny', false);
    $this->addOption('maxx', false);
    $this->addOption('maxy', false);

    $this->addMessage('not_a_image', 'The file is not a image.');
    $this->addMessage('minx', 'Das Bild muss mindestens %minx% Pixel breit sein.');
    $this->addMessage('maxx', 'Das Bild darf maximal %maxx% Pixel breit sein.');
    $this->addMessage('miny', 'Das Bild muss mindestens %miny% Pixel hoch sein.');
    $this->addMessage('maxy', 'Das Bild darf maximal %maxy% Pixel hoch sein.');

    }


    protected function doClean($value)
    {
    $validatedFile = parent::doClean($value);

    // check image size

    $imagedata=getimagesize($value['tmp_name']);

    if (!$imagedata){
    throw new sfValidatorError($this, 'not_a_image');
    }
    if ($this->hasOption('minx') && $this->getOption('minx')!==false && $imagedata[0] < $this->getOption('minx'))
    {
    throw new sfValidatorError($this, 'minx', array('minx' => $this->getOption('minx'), 'size' => (int) $imagedata[0]));
    }
    if ($this->hasOption('maxx') && $this->getOption('maxx')!==false && $imagedata[0] > $this->getOption('maxx'))
    {
    throw new sfValidatorError($this, 'maxx', array('maxx' => $this->getOption('maxx'), 'size' => (int) $imagedata[0]));
    }
    if ($this->hasOption('miny') && $this->getOption('miny')!==false && $imagedata[1] < $this->getOption('miny'))
    {
    throw new sfValidatorError($this, 'miny', array('miny' => $this->getOption('miny'), 'size' => (int) $imagedata[1]));
    }
    if ($this->hasOption('maxy') && $this->getOption('maxy')!==false && $imagedata[1] > $this->getOption('maxy'))
    {
    throw new sfValidatorError($this, 'maxy', array('maxy' => $this->getOption('maxy'), 'size' => (int) $imagedata[1]));
    }

    return $validatedFile;
    }
    }


    In your [model]Form.class.php extend the "configure()"-function with something like this:


    public function configure()
    {
    ...
    $this->widgetSchema['img'] = new sfWidgetFormInputFileEditable(array(
    ...
    ));
    $this->validatorSchema['img'] = new jsValidatorImage(array(
    'required' => true,
    'path' => sfConfig::get('sf_upload_dir').'/images',
    'mime_categories' => 'web_images',
    'minx' => 100,
    'maxx' => 120,
    'miny' => 80,
    'maxy' => 90

    ));
    ...
    }


    The validator will now throw a error if the image-width is not between 100 and 120 pixel or the image-height is not between 80 and 90 pixel

    Donnerstag, 20. Mai 2010

    Googles Web Fonts

    Increasing the diversity of fonts on the web using the Google Font API, the Google Font Directory and the Webfont Loader.

    The selection of fonts used on the Web is very small. This has changed with the new in CSS3 defined @font-face. This allows fonts to be loaded from the web, which works in all modern browsers. This mechanism requires appropriate fonts and the right to make them available on the web or to download from other sources. For this Google has created his Google Font Directory and the Google Font API.

    http://code.google.com/intl/de/apis/webfonts/

    Embedding the font into your page is very easy. Embed the right stylesheet of the font in your page:





    Simply use the font in your CSS font stack like any other font, for example:


    h1 { font-family: 'Lobster', arial, serif; }

    Mittwoch, 31. März 2010

    Filter Admin-List in Symfony 1.4 + Doctrine

    Some time ago I wrote an article about filtering the admin-list in Symfony 1.0 and Propel. Now in Symfony 1.4 and Doctrine the steps to do this are completely different:

    First we have to change the auto-generated file in apps/[APP]/modules/[MODULE]/actions/actions.class.php and to add a function "buildQuery()" to the [module]Actions-class:


    class hotelActions extends autoHotelActions
    {
    protected function buildQuery()
    {
    $query = parent::buildQuery();
    //Here you can extend your $query to filter the result.
    return $query;
    }
    }


    So if we have a "creator_id"-field in our schema and we want to filter the result to show only the entries created by the current user, we have to add the following code:


    class hotelActions extends autoHotelActions
    {
    protected function buildQuery()
    {
    $query = parent::buildQuery();
    $rootAlias = $query->getRootAlias();
    $query->andWhereIn($rootAlias.'.creator_id', Array($this->getUser()->getGuardUser()->getId()));
    return $query;
    }
    }

    Freitag, 27. November 2009

    Firefox implements new W3C File API specification

    Web applications can access local files and upload multiple files.

    The current fourth beta of Firefox 3.6 comes with support for the File API. The relevant W3C draft specification is from Mozilla developer Arun Ranganathan. The File API provides different objects. This includes FileList containing an array of individually responsive files. It should be integrated over an input tag in HTML 5 pages. Several files can be uploaded at once.


    This allows Firefox to upload via drag and drop, so that files can be moved easily from the desktop into a corresponding area in a website. It is also possible to directly create smaller thumbnails in the browser and display, even during the upload of files running in the background.

    Checkout developer.mozilla.org for examples for using the File API.

    Donnerstag, 25. Juni 2009

    Workaround for Flash CS3 Bug in internationalization using Strings Panel : Error 1009

    Flash CS3 has a Bug when creating multilanguage projects using the Strings-Panel with String-replacement-setting set to "Automatically at Runtime".

    A Bug description can be found at http://www.kirupa.com/forum/showthread.php?t=281170
    I did spend a lot of time on trying to find a solution for that problem:


    Assuming you have a dynamic textbox linked to a Strings-Panel-ID in Frame 1 - 5 you will receive a TypeError #1009 if the Player comes to frame 6.


    TypeError: Error #1009: Cannot access a property or method of a null object reference.


    Workaround: The error does not occur if the textbox is embedded in a movieclip that has only one frame.