2009-02-26

Proper Propel behavior registration

Suppose you add a propel behavior using this code:
sfPropelBehavior::add('Article', array(
 'positioned'
));
Now, where to put it? The cookbook suggests it should be put "in lib/model/Article.php". But what if your behavior includes hooks for peer classes, and in a certain action you call peer methods before using model classes (which is rather common)? In this case you might end up without any behavior attached before the first call of any peer method.

This happens because of symfony autoloading mechanism:
  1. Peer class method is called
  2. It is not found, so autoloading steps in
  3. Peer class is loaded
  4. Peer class method is executed
  5. Model class is loaded
  6. Propel behavior is added
  7. Result set is hyrdated
  8. ...
You see: because model class is loaded after the peer class' method executes, there is no behavior attached to peer class at the time of method execution.

Registering behaviors in projectConfiguration doesn't work.

The only solution I found is to register them in plugin's config.php.

Propel Behaviors: registering hooks for doSelectRS

If you read the chapter of symfony cookbook which talks about Propel behaviors, and tried to register a hook for doSelectRS, you'd find out it doesn't work.

In fact, doSelectRS was removed as of symfony 1.2. doSelectStmt took its place, so if you want to add a hook to selecting records, you should write something like this:
sfPropelBehavior::registerHooks('positioned', array(
 'Peer:doSelectStmt:doSelectStmt' => array('wgPropelPositionedBehavior', 'addAscendingOrderByPosition'),
));

2009-02-25

How to get all action variables

For my programming work, I use symfony framework. It has a funny system of passing variables from actions to templates: you just set the variable as a property of your action object and it automatically becomes available in template that is used to render results. So, there is a bunch of variables you can use, but no way of getting the whole array of them. This line will do the trick:
$vars = $sf_context->getActionStack()->getLastEntry()->getActionInstance()->getVarHolder()->getAll();

2009-02-01

Mouse wheel handling with Prototype

There is a bug wandering the web. If you google for "prototype mouse wheel", you'll find some snippets of code. They are rather good, except for their age. Those are '06-'07 oldies, and we have '09 around. Since the time that code was written, there has been a release of Firefox 3, in which they fixed one tiny wee peculiarity: the value of event detail property, which holds the "amount" of scroll. Before the new version of FF it was +-3 for single wheel turn, which is reflected in the old code as the division of detail by 3, which gives "normalized" wheel move data. FF3 guys thought it was a shame to have 3 as value of detail property while in fact it represents only one wheel turn. So they adjusted it to be nice +-1. The problem is, old code returns zero now, because 1/3 equals 0. I will post my fix for this solution, although it's easy as heck to port it to other one.
delta = !!event.detail*(event.detail>0? 1 : -1);
Now this behaves good both in FF2 and FF3.

2009-01-19

semicolon keyCode

When you press semicolon on Firefox and poke in the event object for its key code, the result is 59. The funny thing is, other browsers give you 186. So, if you want to execute some action when user presses semicolon, you have to check for both values.
if (event.keyCode == 186 || event.keyCode == 59) {
   // some stuff
}
Developers often choose to use semicolon in conjunction with CTRL or ALT keys to issue commands to application in a normal "desktop way". For example, CTRL+; activates other keyboard shortcuts in FogBugz. If you are a clean code maniac (as I am), the alternative for semicolon is ' (an apostrophe), which has a nice key code of 222.

2009-01-14

Prototype with TinyMCE: adventures in iframe space

If you use TinyMCE on a fairly large site with a narrow target auditence, there always comes a day when you have to sit down and develop some custom plugins for the rich text editor. For example, if your site is all about Linux and Open Source (like nixp.ru), your users would want to insert code snippets in their posts. This requires some special behavior, which is not provided by default. You have to code it yourself as a custom plugin.

Plugin creation is very closely related to DOM manipulation. TinyMCE has its own DOM API, but it is not nearly as comprehensive as the one provided by Prototype. Being used to walk the DOM with convenient methods like up and down, I hated TinyMCE for the lack of those.

As it turned out, you can't use Prototype methods while developing plugins for TinyMCE. Prototype correctly detects that Firefox 3 has implemented ElementExtensions and SpecificElementExtensions: that means Element.extend, used internally by dollar function, just returns the element, passing the stage of augmenting it with handy methods. The magic happens when designMode of document is set to 'On': Firefox doesn't apply these extensions to elements of such document. This results in awful behavior: you can't use Prototype methods on elements because Element.extend won't copy it to element thinking they are present (hey, Firefox already implemented them!), but the browser returns the elements untouched, just old plain HTMLElements.

My solution to this is a slightly different implementation of dollar function:
$t = (function() {

  var Methods = { }, ByTag = Element.Methods.ByTag;
  Object.extend(Methods, Element.Methods);
  Object.extend(Methods, Element.Methods.Simulated);

  var extend = function(element, force) {
    if (!element || (element._extendedByPrototype && !force) ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName, property, value;

    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;
  }
  
  return extend;
})();
I gave it a different name because there is no need to override the dollar function: since the parent document of an iframe is not in designMode, it does the job properly most of the time. But in plugin development, usage of $t ensures the elements are extended with all the Prototype sugar. Sometimes (particularly, when you insert an extended element into another) the element loses its extended functions. I don't know why it happens. The workaround is the second parameter to $t, called force. This will force reaugmentation of an element disregarding the _extendedByPrototype property.

2009-01-13

Verbose boolean parameters

Suppose we program in Javascript. There is a function updatePost that takes a boolean parameter indicating whether it should highlight the updated post or not.
updatePost: function(highlight) {
  // ...code to update post...
  if (highlight) {
      post.highlight()
  }
}
One would definitely call it like this:
this.updatePost(true);
The problem is, that's not very explanatory. In other words, you can't tell straight from the line what this true does. I propose a solution that takes advantage of loose typing: just pass a string containing a parameter meaning as an argument.
this.updatePost('and highlight');
Now, this is definitily more verbose.