Programming
Most often when I need to sort complex data sets, I'm using some sort of SQL backend. Today I needed to do complex sorting of Reflection objects for some auto generating documentation I'm working on for the upcoming Picora release. Namely, I needed to sort methods according to the following criteria:
- is static
- visibility
- alphabetical order
In the usort() examples, I had always been under the assumption (and from other folks code, I know I'm not the only one) that the return values had to be 0, 1 or -1.
Today I learned that is not true at all, and you can come up with your own scoring system with any range, negative or positive. A truncated, but fully functional example:
class PicoraDocumentationClass extends ReflectionClass {
public function getMethods(){
$methods = array();
foreach(parent::getMethods() as $method)
$methods[] = $this->getMethod($method->name);
usort($methods,array('PicoraDocumentationClass','sort'));
return $methods;
}
static public function sort($a,$b){
$a_score = self::scoreFromMethod($a);
$b_score = self::scoreFromMethod($b);
return ($a_score == $b_score) ? 0 : ($a_score < $b_score ? -1 : 1);
}
static protected function scoreFromMethod(PicoraDocumentationMethod $m){
return array_sum(array(
($m->isStatic() ? -100000 : 0),
($m->isPublic() ? -10000 : 0),
($m->isProtected() ? -1000 : 0),
($m->isPrivate() ? -100 : 0),
ord(substr($m->name,0,1))
));
}
}
The PicoraController class methods when sorted appear would appear in this order:
- static public flash
- static public getFlash
- static public render
- static protected redirect
- static protected renderRSS
- static protected renderJSON
- static protected sendFile
- public afterCall
- public beforeCall
Just as a quick side note, I'm not sure that scoring in orders of magnitude (100,1000,etc) was entirely nessecary, but it did do the trick right, and consistently. Also note PicoraDocumentationMethod is just a subclass of ReflectionMethod, all of those methods being called are in the Reflection engine.
Coming soon to a Picora project page near you...
2 Replies, Posted June 27th, 2007 at 7:31 pm by Ryan
This is an observer, observable implementation for Prototype which I'll be releasing in early june. Kick the tires, and tell me what crimes against JavaScript I've commited.
Control.Modal, Control.Tabs and Control.TextArea will all be re-written with this and bumped to 2.0.
This library is the fusion of a few ideas:
- making any object respond to observe() just like an Element
- making an interface similar to Ajax.Responders usable to other classes
- providing a foundation for many to many object relationships in JavaScript MVC applications
if(typeof(Object.Event) == 'undefined'){
var $stop = new Object();
Object.Event = {
extend: function(object){
object.Responders = {
_objectEventRespondersSetup: function(){
this._responders = this._responders || [];
},
register: function(responder){
this._objectEventRespondersSetup();
this._responders.push(responder);
},
unregister: function(responder){
this._objectEventRespondersSetup();
this._responders = this._responders.without(responder);
}
};
object._objectEventSetup = object.prototype._objectEventSetup = function(event_name){
this._observers = this._observers || {};
this._observers[event_name] = this._observers[event_name] || [];
};
object.observe = object.prototype.observe = function(event_name,observer){
this._objectEventSetup(event_name);
if(!this._observers[event_name].include(observer))
this._observers[event_name].push(observer);
};
object.stopObserving = object.prototype.stopObserving = function(event_name,observer){
this._objectEventSetup(event_name);
this._observers[event_name] = this._observers[event_name].without(observer);
};
object.notify = object.prototype.notify = function(event_name){
var collected_return_values = [];
var arguments_for_observers = $A(arguments).slice(1);
var arguments_for_responders = arguments_for_observers.clone();
arguments_for_responders.unshift(this);
this._objectEventSetup(event_name);
try{
this._observers[event_name].each(function(observer){
collected_return_values.push(observer.apply(observer,arguments_for_observers) || null);
});
if((this.constructor && this.constructor.Responders) || this.Responders){
(this.constructor.Responders ? this.constructor.Responders : this.Responders)._responders.each(function(responder){
if(responder[event_name])
collected_return_values.push(responder[event_name].apply(responder,arguments_for_responders) || null);
});
}
}catch(e){
if(e != $stop)
throw e;
}
return collected_return_values;
};
}
}
}
Sample Life Cycle Callback Usage
The first obvious use case as stated above is in object lifecycles.
Control.Tabs = Class.create();
Object.Event.extend(Control.Tabs);
Object.extend(Control.Tabs,{
setActiveTab: function(){
this.notify('beforeChange');
//tab changing logic here
this.notify('afterChange');
}
});
Control.Tabs.Responders.register({
beforeChange: function(tabs_instance){
//called for every tabs instance
}
});
tabs = new Control.Tabs();
tabs.observe('beforeChange',function(){
//called just for this one tabs instance
});
Usage as Foundation for JavaScript MVC
The talk I am giving in July at the AJAX experience will go into this in much greater depth, but the other use case is treating any internal event in your application as something that is observable. Among the key benefits:
- very clean separation of concerns
- you can add an unlimited amount of event handlers as the application grows
- the code that pertains to a given object can all be written in the same place
For example, let's say we have a UserController object, and a number of things need to be displayed or hidden based on the login state:
UserController = {
login: function(){
//do login
$('element1').show();
$('element2').show();
},
logout: function(){
//do logout
$('element1').hide();
$('element2').hide();
}
};
If you treat the login and logout methods as events (the events can be called anything, the names don't need to match), you can seperate out the code that deals with the business logic, and the display logic.
UserController = {
login: function(){
//do login
UserController.notify('login',user_data);
},
logout: function(){
//do logout
UserController.notify('logout');
}
};
Object.Event.extend(UserController);
//elsewhere in your app
UserController.observe('login',function(){
$('element1').show();
$('element2').show();
});
UserController.observe('logout',function(){
$('element1').hide();
$('element2').hide();
});
For a 20 line script, this doesn't matter one bit. I'd gladly code it the first way. But if you're dealing with thousands of line of JS spread over multiple files, it's the only way to survive.
4 Replies, Posted May 24th, 2007 at 12:24 pm by Ryan
Well, not really hidden. You just have to look hard. The new DOM Builder addition got me looking at the branches in the source tree. These function prototype extensions are absolutely awesome, I hardily hope they make it into the core at some point. There is also a port of Ben Nolan's Behaviour library that seems totally ready.
The core team seems fairly conservative about what goes into a new release. I'm not all for throwing in the kitchen, sink, but there are a limited number of additional things that I find myself wanting, and most of them are present in various patches by now.
Maybe I should change the name of Prototype.Tidbits to Prototype.Liberal and incorporate a few of the unused patches.
1 Reply, Posted May 17th, 2007 at 12:29 pm by Ryan
After many years of doing this, I seem to have settled on this pattern for the last few years. If everyone used this, our web browsing lives would be so much easier, productive and fun.
Code is slightly updated from the original post to reflect the comments below.
<a href="your_link" onclick="return !window.open(this.href,'window_name','options');"></a>
Try It
Why is this so awesome? If the browser doesn't have JavaScript enabled, it still works. For anyone who just clicks and does have JavaScript enabled, you get control of how the window opens, but most importantly, anyone who WANTS control of how the window is opened still gets it. Even if you want your window to be opened with no toolbars, and be exactly 400 pixels tall, I may really want it in another tab. And at the end of the day it's my screen. Give me the dignity to choose.
Note this still works just fine when defined entirely in JavaScript, but you can't attach it with addEventListener / attachEvent / observe. It has to be like this:
link.onclick = function(){
return !window.open(this.href,'name','options');
};
If you don't do it this way, you'll open a window with the link, and the parent window will still follow the link too.
In the comments below there is an example with Prototype's observe method.
9 Replies, Posted May 14th, 2007 at 7:04 pm by Ryan
I haven't heard much news about the next version of PHP, but this my wish list.
Closures
So far all of the rumblings have been about closures not being "the PHP way", but I suspect the real reason is that it's just far to difficult because of the way the engine is built. In leiu of closures, any inline function declaration, even if it isn't scoped at all, and the ability to pass a function around as a variable would be insanely useful.
$my_function = function(){}
$my_function();
function named_function($callback){
return $callback();
}
named_function(function(){});
Some of that is already legal. It's 2007, and create_function is so crazy stupid that I forget lambadas exist while I am coding PHP sometimes.
{ } is the new array()
It would be oh so nice to use the curly brackets as a hash/array/evil nested data type, just like everybody else. While we're on the subject, this would be even more dandy:
$my_array = {a: 'one', b: 'two'};
Parameter Collection in Functions
Keyword arguments are out, and that's fine. But could we pretty pretty please have this:
function my_function($one,$everything_else = array()){}
my_function('argument_one','these'=>'all','collect'=>'into','an'=>'array');
This is legal:
array('argument_one','these'=>'all','collect'=>'into','an'=>'array');
And I'm assuming internally that function arguments have got to be some sort of array, so why not?
Late Static Binding
I'm pretty sure I've read this is on the big list, but man on man do we need it.
Article::find(1);
really needs to be possible instead of
PicoraActiveRecord::find('Article',1);
Backwards Compatability
I'm not expecting it at all. But, why not bundle the 4.x and 5.x engines all into the same binary, and let us flag a script as being a certain version. Perhaps a nightmare. Just a thought.
14 Replies, Posted May 10th, 2007 at 10:15 pm by Ryan
I was checking out the new Google Analytics release, and beyond being really impressed with the app as a whole (and how easy it was for me to figure out all of these data points), I was impressed at how skewed the browser statistics for this site are compared to the mainstream.
- 68% Firefox (90% 2.x, 10% 1.5)
- 22% Internet Explorer (56% 6.x, 43% 7.x)
- 4% Safari (94% 2.x, 3% 1.x)
- 3% Opera (99% 9.x)
And the rest were all Mozilla based variants. I haven't testing against IE 5.x in a while, and I was so very very (very) pleased that of the roughly 125 thousand human beings (or robots) that have visited the site in the last few months, only 7 people visited the site with IE 5.x.
Despite this being a site for developers (and therefore people who care about their browsing experience), I'm glad I can finally drop any straggling worries about IE 5 being relevant.
3 Replies, Posted May 10th, 2007 at 1:52 pm by Ryan