Thursday, June 24, 2010

Zend Form: display group and custom decorator

I keep meaning to write a proper review of Zend Framework, but I am waiting to finish a big project that uses it. It is running late, and I think ZF can take some of the blame. So don't hold your breath waiting for a positive review.

Today's topic: I want to have part of my form hidden initially, and instead show a button saying: "toggle more questions".

So, we make it a display group (where ex1 and ex2 are the form elements to hide):
$form->addDisplayGroup(array('ex1','ex2'),'extras');

By default this wraps it in a fieldset, with no place I could see to slip in my CSS, javascript and the toggle link. What I need is a decorator, I said to myself.

Oh, gasp, does Zend make this difficult or what! In another part of this project custom decorators had been used for a minor layout change. 7 classes in 3 directories, almost all of it boilerplate. The real work was being done in CSS; those 7 classes were just to give a way to name the items as far as I could tell.

The problem is the Zend Framework Philosophy of making things complex. Did you realize there is no addDecorator($myclass) function! You have to keep decorators in a special directory and then tell Zend where to get them. Then addDecorator('part_of_my_class_name').

ZF's saving grace is that it is open source. So I poked around, and here is my solution. First, I'll define an alias for readability (optional):
$displayGroup=$form->getDisplayGroup('extras');

Remove all the default stuff I don't need (this step is optional too):
$displayGroup->removeDecorator('Fieldset');
  $displayGroup->removeDecorator('HtmlTag');
  $displayGroup->removeDecorator('DtDdWrapper');

Now insert my decorator (these 4 lines are in lieu of addDecorator($myclass)):
$decorators=$displayGroup->getDecorators();
  $decorators['MyTest']=new MyTestDecorator();
  $displayGroup->clearDecorators();
  $displayGroup->addDecorators($decorators);

(I.e. get the current decorators, add mine, then replace the existing decorators with the new set.)


Finally we get to the meat. All you need is to define a render() function that takes a string (the existing content) and returns that string, optionally modified. Here is the minimal version that does nothing.
class MyTestDecorator extends Zend_Form_Decorator_Abstract
{
  public function render($content){ return $content; }
}

And here is the full version: it hides the elements in the group and uses JQuery to show/hide it. The CSS is inline.
class MyTestDecorator extends Zend_Form_Decorator_Abstract
{
  public function render($content)
  {
    $js="$('#extra_questions').toggle();return false;";
    return '<a href="" onclick="'.$js.'">Toggle Tags Visibility</a>'.
      '<div id="extra_questions" style="display: none;">'.$content.'</div>';
  }
}

Yep, it's that simple. Oooh, the architects of ZF must be turning red with rage ;-)

UPDATE: The best article I've found so far on Zend Form Decorators. As many of the comments say, the length of the article is also a very good argument against using Zend Form. And it still didn't answer my questions, so it really needed to be 2-3 times as long. But if you need to format a form, it is a far more useful resource than the utterly inadequate Zend Form manual.

Tuesday, June 22, 2010

fclib 0.4.20 release

I just put up another fclib release. Fclib is an ad hoc collection of php libraries, started about 10 years ago; the i18n-related functions are perhaps of most interest to people (with functions for Japanese, Chinese and Arabic language processnig).

This new release has some minor bug fixes, a new utf8 function (for truncating a string), and a new file, modify_images.inc, that is a high-level interface to use gd functions to make doing thumbnails, cropping, resizing and basic drawing edits. It can operate on one or a batch of images.

(The previous release was 11 months ago, described here)

Monday, June 21, 2010

C++ socket library: asio

It is when it comes to writing a simple socket client in C++ that I really feel how much PHP has spoilt me.

I previously have used SmartNetwork, part of the SmartWin library ( http://smartwin.sourceforge.net/ ) but it is fatally flawed: when the remote server dies there is no error reporting, so it happily carries on sending data to oblivion.

Therefore when an application I am currently working on needed to write to a socket I decided to try out a new library. I looked at 4 or 5 libraries, and after that first pass rejected all of them. Mainly based on their lack of clear documentation; sometimes based on their license. Faced with a choice of zero, I lowed my criteria, and chose to try boost::asio (also available in a non-boost version). Boost is a major project, already installed on my machines, portable for at least Linux/Windows, and the libraries are peer-reviewed by some very clever people. Boost libraries are also usually flexible (to the point of making them hard to use), and definitely able to give me the error reporting I need.

Here is the one line review: I've been banging my head against ASIO for 2-3 weeks, still do not have working code, but every time I consider running away I decide to stick with it.

Or in other words, it is terrible, but the alternatives are worse.

It is terrible in three ways: no high-level functions, it is hard to use properly and it is basically undocumented. Yeah, yeah, there is API documentation for all the functions, but it doesn't say when to use which function. And, yeah, yeah, there are a dozen or so tutorials. But they are all toy examples, and don't explain why the code has been written the seemingly-complex way it has.

Let me explain a bit more. Asio offers sync operations, but they are no use for any real-world code. For something you intend to use in production you will be forced to use the async functions. That means you'll need to use boost::threads, boost::bind (for the callbacks), boost::smart_pointer (as you have to wait for all callbacks to finish before you can delete an object, and it turns out your async callbacks can get called even after you've closed the socket) and understand how async programs work. All of that is hard.

I keep thinking I've got the code working, then I discover a timing problem that causes a crash only once in 15 runs, or it works on Linux but crashes on Windows, or is fine connecting to localhost but crashes when connecting to a remote server (due to different timing).

Going back to the first of my reasons for describing it as terrible, an example of the lack of high-level assistance is that there is no timeout parameter on any async operations. To do an async_connect that I want to give up after 3 seconds I have to write code for both the connect and the timer, which means two callbacks, and coordinating those two callbacks. Time-outs are part of the low-level BSD sockets but the asio code is doing something that deliberately cripples that, so trying to use them won't work either.

But I don't like to moan without being constructive. So I've been working on two things. First a high-level class called SocketFeeder (and SocketFeederSet) that you can just call write() on and not have to worry about all these callbacks. Second, a tutorial explaining how SocketFeeder has been written, and the thinking behind the design decisions.

The class is for a client's production environment but has been written on my own time, so I'll be able to release it as open source. I'll edit this blog to link to it when it is finally ready; if you are keen to see it and the tutorial then leave a comment or send me a mail. Constructive nagging usually works! (And if you want to sponsor me, or are a magazine that pays for articles, let me know; it will get released eventually, but financial incentive means I can give it priority.)

Tuesday, June 15, 2010

doctrine: delete object but leave it in database

I've a website where I make a page from a doctrine object which is taken from a database. Nothing special there. For adding the data to the database I have a form that goes to a preview page then a confirm button that actually writes it. For the preview page I share the same display logic, so I construct a doctrine object and simply don't save it. For editing, I do the same: I load the doctrine object from the database, make the changes specified on the form, and then either show the preview page or save to the database (depending on if preview or confirm was clicked).

The problem came with editing a one-to-many relation. As a concrete example let's say the user is registering multiple email addresses. I was doing this:
   $User->Emails->delete();

Then doing this for each address the user gave:
   $User->Emails[]=new Email(...);

It nicely handles when the user has removed an email address, changed one, or added an extra one. But when I realized the flaw I slapped my forehead so hard I gave myself whiplash. Do you see it? Have a moment...

Yep, if they click cancel (or leave before clicking confirm) they're expecting nothing has changed, whereas in fact all the email addresses they'd previously input have vanished.

The problem is that delete() happens immediately, rather than waiting for me to call save(). I hunted high and low for a way to stop that. My final solution was:
   if(confirmed)$User->Emails->delete();
   else $User->unlink('Emails');

I.e. unlink() appears to be the delete-that-does-not-touch-database I was searching for. (Of course, don't do something silly like go and call save() now, or the user will lose their email addresses and you'll have some orphaned records in your Emails table.)

Incidentally this did not work:
 $user->clearRelated('Email');

I'd hoped it would, after reading the description in the manual. But in fact it did nothing at all.

Friday, June 11, 2010

jquery: dcookorg_annotator V0.3 released

I know, I'm behind in my jquery plugin announcements, so I'm going to do three in one.

First up is annotator, for annotating an image (or anything):
http://dcook.org/software/jquery/annotator/

You can have any number of annotations, can drag them anywhere, and can resize them. In the default mode each annotation has a text box appear underneath it for adding a comment.

New in version 0.3, and shown in the screenshot to the left, are some hooks for attaching a form to each annotation, so you can create a custom form for each one (or any other idea you have)!

MIT-license open-source, and tested in all of IE6, IE7, IE8, Safari, Firefox 3 and Firefox 3.5.




Next up is selector_aspect, which is for selecting part of an image while maintaining a fixed aspect ratio. E.g. useful for cropping an image.
http://dcook.org/software/jquery/selector_aspect/

A simple straightforward plug-in, with not many options.

Third is get_percentage_position, which is used to get the size of one div (or any DOM position) in terms of another div (or any DOM object), and also to get the relative position in the same terms. Not very glamorous, but useful in conjunction with the selector_aspect plugin, for instance. It is available here:
http://dcook.org/software/jquery/get_percentage_position/

Finally, a reminder that my first jquery plugin, to run a magnifier over an image is introduced here:
http://darrendev.blogspot.com/2010/04/jquery-plugin-image-magnifier.html

and available here:
http://dcook.org/software/jquery/magnifier/

and all my jquery plugins are being kept here:
http://dcook.org/software/jquery/

Sunday, June 6, 2010

C++: incomplete type and cannot be defined

A very confusing error just now:
error: aggregate ‘std::ofstream out’ has incomplete type and cannot be defined

from simple code:
std::ofstream out;

And the similar one:
error: variable ‘std::ofstream out’ has initialiser but incomplete type

from this code:
std::ofstream out(fname.c_str(),std::ios_base::app);

Using this didn't help:
#include <iostream>

Using this also didn't help:
#include <ofstream>

That was confusing me most, but now I see I've been getting "error: ofstream: No such file or directory" and I was missing it in the noise of other warnings and errors.

The solution was simple:
#include <fstream>


Yes, a very simple solution, but google wasn't helping. If it had been saying "ofstream is not a member of std" I'd have known I was missing a header file; a strange error message has you looking in other places. (I guess another std header file is doing a forward declaration for ofstream, which is why we get "incomplete type".)

Mumble, grumble, back to work.