So this may be very Merlin Man (site, twitter, podcast1, podcast2, and podcast3) inspired, but I have to ask it, just for everyone’s sanity: What if you’re actually terrible at what your real job is?
Let’s say you’re hired to build websites. sure. You’re hired to build websites. But as Horace Dediu (podcast) points out, a lot of what we need to look at is the “Jobs to Be Done” and when you look at it in that perspective, your Job to Be Done is really to solve their communication issue.
That’s right. You’re not building a website, but communicating information. Be it “This is good” or “That is bad”, or just “Buy this widget”, the job you are doing for your employer is helping them to disseminate information.
If you get up every day, build exactly what they tell you to build, exactly the way they say, there’s two very, very real possibilities of what is happening:
You’re viewed as nothing more than a production worker
You’re terrible at actually solving the problems given to you, and thus become #1
Sure. You have to be able to make things pretty to be a designer, and you have to be able to code to be a programmer, but you have to be able to problem solve to be effective at either.
So, what do you do if you decide you’re terrible at your job?
With no javascript, nothing but the <img> tag is displayed, and everything is happy.
With javascript, we run a system which turns it into the following (this becomes easier if we can evaluate media queries in JS, but I’m not sure if we can do that, so I’m showing an alternate way):
Which should make it happy with screen readers and other similar systems.
This approach has a few distinct advantages:
It leverages existing tools for defining when to load a particular image (media queries)
It provides a graceful fallback for no javascript support
It (hopefully) will be relatively close to whatever is selected for moving forward in a browser integrated solution, as it fits with the patterns already established.
So that’s it. That’s my master plan. What do you think?
If you have (or use) a Brother printer, you know EXACTLY what I am talking about.
EVERY time you restart your computer, you get the obnoxious control center which wants to run an install (on a printer you probably don’t have connected) and takes a minimum of 2 (3 in my case) clicks to dismiss, and it’s slow to boot!
Then you remember, click on the system icon, go to preferences, uncheck display at start up, hit ok, and you’re done.
Then you reboot again and BAM! There’s Brother right back in your face with the obnoxious control center again.
You angrily click through to close it out, go BACK to preferences, UNcheck the startup option, and hit Ok. Now, because you’re NOT crazy, it’s time to make sure it saved what you did. You go BACK to preferences again AND IT IS STILL CHECKED!
Come ON!
But let me share a secret with you.
Let me save your sanity!
Open up a terminal, and type the following:
mkdir ~/Library/Preferences/Brother/
Now, go back, one last time, into Preferences, and uncheck on startup, and press Ok.
Ok, fine, you can go check it again, but look! It’s listened to you!
We’re going to step things up a notch with this one, so hang on for the ride, because for those of you not already well versed in symfony and jQuery, there will be a lot of new information, and it will go pretty quickly, but I will try to make sure to keep everyone caught up. This is another post I hinted towards back when I first wrote about symfony widgets. Right, so, on to the fun!
First things first, let’s talk about requirements. My only real requirement is that it must be seamless. We must be able to only implement the widget, and have everything else work exactly as intended. For a date widget, this means when the user clicks that submit button, an array with ‘month’, ‘day’, and ‘year’ must be sent back. Not a string, not JSON, just that basic array.
It’s for the above reason that we will need to make a widget on top of the basic jQuery UI Datepicker Widget. The Datepicker alone would just return a string when we submitted the form, which would then require at least a custom validator, if not much more. So to simplify things later, we’ll put in more effort now.
There are four things we will be doing in this tutorial to make this happen. Here’s a quick overview:
Set up a new module for us to use with the widget tutorials
Create our widget class
Create our jQuery UI widget that goes along with our widget class
1. Setup majaxJqueryPlugin
We’re using my plugin for two reasons:
It’s my blog, so of course, I’m using my plugin
We need jQuery UI, both the JavaScript and the CSS, and majaxJqueryPlugin provides that right away
Setting it up is easy:
cd plugins
wget-O majaxJqueryPlugin.zip http://jmather.com/wp-content/plugins/download-monitor/download.php?id=4
Now we add the plugin to our Project Configuration. Open up your config/ProjectConfiguration.class.php file, and place this line somewhere within your setup() function:
$this->enablePlugins('majaxJqueryPlugin');
And one last step, we need to publish the assets:
./symfony plugin:publish-assets
Alright, now we’re ready to move on to the good stuff.
2. Setting up our plugin
Now we need to make a new plugin, and set up a few directories for future use. It’s pretty self-explanatory so we’ll cut right to the code, and come back afterwards to talk about some of the extras.
We made our plugin, then we made the directory our jQuery plugins will go in, and the directory where we will store our widgets. The -p flag means “no error if existing, make parent directories as needed” which saved us a few mkdir commands.
3. Making our symfony widget
Now open up plugins/majaxWidgetPlugin/lib/widget/majaxWidgetFormDate.class.php and here’s our code:
So, to start out, we extend sfWidgetFormDate, as that will make the controls we need to manage a date submission. The only change we need to do, is to override the render function, which will let us add some custom code around the standard output, to allow us to more easily control the display.
The first thing we do in the render function is to include the required JavaScript. Some will say the widget shouldn’t know about the JavaScript, but I say, if the widget shouldn’t know… who should? We want to make this as transparent as possible, so why should we have to also remember to add a JavaScript include to use this widget?
Next, we grab our generated ID, and figure out our options (i.e. can the date be null) that we can pass along to Datepicker. After that we wrap the whole deal in a containing div, and then wrap the native controls in another specific div. You will see why we did this in a minute. Lastly, we write the JavaScript to activate the widget.
4. Making our jQuery Plugin
As is the standard, I’ll show you the code, then highlight portions that are interesting. This file goes in plugins/majaxWidgetPlugin/web/js/jquery.majax.dateselector.js
You know, looking over, it’s pretty clear, I feel, what most parts do. The functions “_hide_real_ctrls”, and “_show_real_ctrls” hide and show that wrapping div we built around the original controls, so we can keep them in play, but not have to worry about controlling them. The “_build_facade” function builds our fake interactive Datepicker object, and optionally our ‘Clear’ button, if our date is allowed to be empty. Functions “_clear_display” and “_update_ctrls” to exactly as you would expect. Cure functions “_create” and “destroy” are from the jQuery UI Widget framework, and are called … can you guess when? 🙂
We’re done!
Once you’re done, you can replace any sfWidgetFormDate instance with a majaxWidgetFormDate instance, and everything else is handled. A little bit of effort up front, and many rewards down the road!
One of the questions I see quite frequently is “How do I customize the user management interface for sfDoctrineGuard?” There are many different ways to go about it, but let me tell you about mine.
If you know me, you know I’m about attention to detail. I want the absolute cleanest solution you can come up with, and then I want it to look polished. So what you’re about to see will be quite a bit more involved than you would normally expect for such a tutorial, but I believe you will agree, at the end, that it was well worth the trip.
The first step is to get ready to customize. It’s easy, I promise. Here we go!
Note: pay special attention to integer(4). If you forget this, MySQL (and I assume other databases) won’t be able to build the relation properly as the field types will not match.
Now we need to rebuild things. If you don’t know how to do a migration, here’s the code you want:
Your sfDoctrineGuardUserProfile is ready to go, so let’s build our new admin form.
2. Add embedMergeForm to BaseFormDoctrine
The embedMergeForm function originates from (I believe) Roland Tapken of Cybso in this post. It’s a great piece of code. It gives us the function of embedded forms, with the look of merged forms. Here is the code I do (also adapted with one of the fixes from the comments section):
<?php// File: lib/form/doctrine/BaseFormDoctrine/**
* Project form base class.
*
* @package dcms
* @subpackage form
* @author Jacob Mather
* @version SVN: $Id: sfDoctrineFormBaseTemplate.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
*/abstractclass BaseFormDoctrine extends sfFormDoctrine
{/**
* Embeds a form like "mergeForm" does, but will still
* save the input data.
*/publicfunction embedMergeForm($name, sfForm $form){// This starts like sfForm::embedForm$name=(string)$name;if(true===$this->isBound()||true===$form->isBound()){thrownew LogicException('A bound form cannot be merged');}$this->embeddedForms[$name]=$form;$form= clone $form;unset($form[self::$CSRFFieldName]);// But now, copy each widget instead of the while form into the current// form. Each widget ist named "formname|fieldname".foreach($form->getWidgetSchema()->getFields()as$field=>$widget){$widgetName="$name-$field";if(isset($this->widgetSchema[$widgetName])){thrownew LogicException("The forms cannot be merged. A field name '$widgetName' already exists.");}$this->widgetSchema[$widgetName]=$widget;// Copy widget$this->validatorSchema[$widgetName]=$form->validatorSchema[$field];// Copy schema$this->setDefault($widgetName,$form->getDefault($field));// Copy default valueif(!$widget->getLabel()){// Re-create label if not set (otherwise it would be named 'ucfirst($widgetName)')$label=$form->getWidgetSchema()->getFormFormatter()->generateLabelName($field);$this->getWidgetSchema()->setLabel($widgetName,$label);}}// And this is like in sfForm::embedForm$this->resetFormFields();}/**
* Override sfFormDoctrine to prepare the
* values: FORMNAME|FIELDNAME has to be transformed
* to FORMNAME[FIELDNAME]
*/publicfunction updateObject($values=null){if(is_null($values)){$values=$this->values;foreach($this->embeddedFormsAS$name=>$form){foreach($formAS$field=>$f){if(isset($values["$name-$field"])){// Re-rename the form field and remove// the original field$values[$name][$field]=$values["$name-$field"];unset($values["$name-$field"]);}}}}// Give the request to the original method
parent::updateObject($values);}}
Save your file, and let’s move on to step three.
3. Build our new interface form.
Let’s make a new file, lib/form/doctrine/myGuardUserAdminForm.class.php, and fill it with the following:
Now all you have to do is clear your cache, and go to your sfGuardUser form page, and you will see your new field!
5. Cleaning up messes.
Remember back in the schema.yml where we specified the onDelete: CASCADE? Bad news, that probably didn’t take care of it. There’s a bug somewhere between Symfony and Doctrine where sometimes foreign key constraints aren’t always handled appropriately. To counteract that, we will just run an SQL command to correctly establish the relationship so that when users are removed, we also remove their auxiliary profile data.
And we’re done! See? It was quite a long trip, but I hope you agree with me, the result is worth it.
02/16/2011 – Note: A thanks to hectorh30 from #symfony for pointing out an error in step three regarding the name of the profile form. It has been corrected.
02/12/2011 – Note: And in step one about paths. Thanks hectorh30!
03/03/2011 – Note: Thanks to dmclark for pointing out myDoctrineGuardAdminForm should be myGuardUserAdminForm
03/28/2011 – Note: Thanks to Richard Linkster for pointing out a copy command that was all busted up.