2 minutes admin layout with rails and the web-app-theme generator

July 30th, 2009 by Andrea Franz

Many people found out a rails generator inside my web-app-theme project and asked me how to use it.
Here an example, starting from scratch with a new rails app that manages music Albums.

rails cool_albums
cd cool_albums
script/generate scaffold Album name:string artist:string date:date
rake db:migrate

After creating the first controller with a scaffold or with your hands, start creating a theme:

script/plugin install git://github.com/pilu/web-app-theme.git
script/generate theme application --app_name="My Cool Albums" --theme="drastic-dark"

The first argument (“application”) is the name of the layout that the generator will create (application.html.erb).
The –app_name option specifies the name used as page title, and with the –theme specifies which theme to use among all the available themes inside the plugin.

Now remove the default index.html created by rails and the layout created by the scaffold:

rm app/views/layouts/albums.html.erb 
rm public/index.html

Add the following line in your routes.rb to set the default page of the application:

map.root :controller => :albums

Start the server

script/server

Ok, the layout has been successfully created, but we need to apply a theme for each one of the views generated by the scaffold.

script/generate themed albums album --layout=application --with_will_paginate

With the first 2 arguments I specified the controller path (albums) and the model used (album).
The –layout options is used by the themed generator to know where to add the Albums menu link.

Since we want to use will paginate (we set the –with_will_paginate option), we need to change one line in our albums controller from:

@albums = Album.all

to:

@albums = Album.paginate(:per_page => 10, :page => params[:page])

Here a trick to show form error messages inside the auto generated forms, you can add the following lines in your environment.rb:

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| 
  if html_tag =~ /<label/
    %|<div class="fieldWithErrors">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>|
  else
    html_tag
  end
end

Ok, restart your server and you are done.

Feel free to fork the project from github to improve the generator or to add a new theme.


Creating PDF with images using flex and AlivePDF

July 19th, 2009 by Andrea Franz

Today I’m working on an AIR application, and I need to create a PDF starting from a list of images. This PDF needs to have one image for each page. I used AlivePDF, a very useful ActionScript3 library for PDF generation, and I created a simple class that takes an array of image paths and creates the PDF I need.

Here the class I use:

package
{	
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
  import flash.net.URLLoader;
  import flash.net.URLLoaderDataFormat;
  import flash.net.URLRequest;
  import flash.utils.ByteArray;
 
  import org.alivepdf.pdf.PDF;	
  import org.alivepdf.display.Display;
  import org.alivepdf.images.ResizeMode;
  import org.alivepdf.layout.Layout;
  import org.alivepdf.layout.Orientation;
  import org.alivepdf.layout.Size;
  import org.alivepdf.layout.Unit;
  import org.alivepdf.saving.Method;
 
  public class PdfGenerator extends EventDispatcher
  {		  
    private var pdf:PDF;
    private var images:Array;		
    private var outFilePath:String;
    private var completedPages:int;
 
    public function PdfGenerator(images:Array, outFilePath:String)
    {
      this.outFilePath = outFilePath;
      this.images = images;
    }				
 
    public function generate():void
    {						
      this.completedPages = 0;
      this.pdf = new PDF(Orientation.LANDSCAPE, Unit.MM, Size.A4);
      this.nextPage();		
    }
 
    private function nextPage():void
    {
      this.completedPages < this.images.length ? this.loadImage(this.images[this.completedPages++]) : this.save();
    }
 
    private function loadImage(path:String):void
    {						
      var urlLoader:URLLoader = new URLLoader();			
      urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
      urlLoader.addEventListener(Event.COMPLETE, this.onImageLoadComplete);
      urlLoader.load(new URLRequest(path));						
    }
 
    private function onImageLoadComplete(event:Event):void
    {						
      this.pdf.addPage();
      this.pdf.addImageStream(ByteArray(event.target.data), 0, 0, 0, 0, 1, ResizeMode.FIT_TO_PAGE);
      this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, false, false, this.images.length, this.completedPages));
      this.nextPage();
    }
 
    private function save():void
    {				
      this.pdf.end();
      this.writeOutFile();												
      this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE));
    }
 
    private function writeOutFile():void
    {
      var fileStream:FileStream = new FileStream();								
      fileStream.open(new File(this.outFilePath), FileMode.WRITE);			
      fileStream.writeBytes(this.pdf.save(Method.LOCAL));
      fileStream.close();
    }
  }
}

Basically for each image it creates a new URLRequest and when the request is completed it adds a new page with the image. As you can see I also dispatch a custom Event called PdfGeneratorEvent. That because I need to create a PDF with a large number of images, and doing like this I can give a feedback to the user with a ProgressBar that display the generation progress.

package
{
  import flash.events.Event;
 
  public class PdfGeneratorEvent extends Event
  {			
    public static const PDF_GENERATOR_PAGE_COMPLETE:String = "PDF_GENERATOR_PAGE_COMPLETE";
    public static const PDF_GENERATOR_COMPLETE:String      = "PDF_GENERATOR_COMPLETE";			
 
    public var totalPages:int;
    public var completedPages:int;
 
    public function PdfGeneratorEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false, totalPages:int=0, completedPages:int=0)
    {			
      super(type, bubbles, cancelable);
      this.totalPages     = totalPages;
      this.completedPages = completedPages;
    }
  }
}

And here the main file that uses this class:

public function createPdf(images:Array):void
{
  var generator:PdfGenerator = new PdfGenerator(images, "/path/to/my/test.pdf");
  generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE, this.onPdfComplete);
  generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, this.onPdfPageComplete);				
  generator.generate();				
}
 
public function onPdfPageComplete(event:PdfGeneratorEvent):void
{					
  progressBar.setProgress(event.completedPages, event.totalPages);
  progressBar.label = event.completedPages + " of " + event.totalPages;															
}
 
public function onPdfComplete(event:PdfGeneratorEvent):void
{											
  trace("completed")
}

Cocos2d: how to make a label to fit the Iphone screen width

July 15th, 2009 by Andrea Franz

In the iPhone game I’m working on I need to display a different label every time I start a new game level. Each one of these labels has a random string taken from an array of words, and each one of these strings has different size. So every time I create a new label I set the maximum font size and then I decrement it until the string size measures less than the iPhone screen. I do that using the sizeWithFont method of the NSString class:

- (int) calculateFontSizeForString:(NSString*)string fontName:(NSString*)usedFontName {
  int fontSize = 120; // it seems to be the biggest font we can use
  while (--fontSize > 0) {			
    CGSize size = [string sizeWithFont:[UIFont fontWithName:usedFontName size:fontSize]];
    if (size.width <= 480 && size.height <= 360)
      break;
  }				
 
  return fontSize;
}

And here a custom scene that you can use to test it:

MyScene.h

#import "cocos2d.h"
 
@interface MyScene : Scene {
  Label    *label;
  NSArray  *strings;
  NSString *fontName;
  int      stringIndex;
}
 
- (void) nextLabel;
- (int)  calculateFontSizeForString:(NSString*)string fontName:(NSString*)usedFontName;
- (void) removeCurrentLabel;
- (void) createNextLabel;
- (void) animateLabel;
 
@property (nonatomic, retain) NSArray  *strings;
@property (nonatomic, retain) NSString *fontName;
@end

MyScene.m

#import "MyScene.h"
 
@implementation MyScene
 
@synthesize strings;
@synthesize fontName;
 
- (id) init {
  if (self = [super init]) {
    [self setFontName:@"Marker Felt"];
    [self setStrings:[NSArray arrayWithObjects:
                      @"Lorem ipsum", 
                      @"Lorem ipsum dolor",
                      @"Lorem ipsum dolor sit amet",
                      @"Lorem ipsum dolor sit amet, consectetur adipisicing elit",
                      @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                      nil]];		
    stringIndex = 0;
  }
 
  return self;
}
 
- (void) onEnter {
  [super onEnter];
  [self nextLabel];
}
 
- (void) nextLabel {	
  [self removeCurrentLabel];
  [self createNextLabel];	
  stringIndex++;	
}
 
- (void) removeCurrentLabel {
  if (label) [self removeChild:label cleanup:YES];
  label = nil;
}
 
- (void) createNextLabel {
  NSString *labelString = [strings objectAtIndex:(stringIndex % [strings count])];
  int fontSize = [self calculateFontSizeForString:labelString fontName:fontName];	
  label = [Label labelWithString:labelString dimensions: CGSizeMake(0, 0) alignment: UITextAlignmentCenter fontName:fontName fontSize:fontSize];
  [label setPosition:ccp(240, 160)];	
  [label setScale:0.5];
  [self addChild:label];
  [self animateLabel];	
}
 
- (void) animateLabel {
  IntervalAction *scale = [ScaleTo actionWithDuration:0.5 scale:1];
  id delay  = [DelayTime actionWithDuration:1]; 
  id notify = [CallFunc actionWithTarget:self selector:@selector(nextLabel)];	
  Sequence *sequence = [Sequence actions:scale, delay, notify, nil];	
  [label runAction:sequence];		
}
 
- (int) calculateFontSizeForString:(NSString*)string fontName:(NSString*)usedFontName {
  int fontSize = 120; // it seems to be the biggest font we can use
  while (--fontSize > 0) {			
    CGSize size = [string sizeWithFont:[UIFont fontWithName:usedFontName size:fontSize]];
    if (size.width <= 480 && size.height <= 360)
      break;
  }				
 
  return fontSize;
}
 
- (void) dealloc {
  [strings retain];
  [fontName retain];
  [super dealloc];
}
 
@end

FitFontTestAppDelegate.m

#import "FitFontTestAppDelegate.h"
 
@implementation FitFontTestAppDelegate
 
- (void)applicationDidFinishLaunching:(UIApplication *)application {
  window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  [window setUserInteractionEnabled:YES];
  [window setMultipleTouchEnabled:YES];
 
  [[Director sharedDirector] setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
  [[Director sharedDirector] attachInWindow:window];		
  [window makeKeyAndVisible];	
 
  MyScene *scene = [MyScene node];
  [[Director sharedDirector] runWithScene:scene];
 
}
 
 
- (void)dealloc {
  [window release];
  [super dealloc];
}
 
@end