Posts Tagged ‘PHP’

Forms, simplified

Monday, September 28th, 2009

http://www.marginallyclever.com/forms/

A short while ago I posted about my PHP form system. People wrote in with many questions, leading me to the conclusion that the old system with it’s klunky javascript, lack of commenting, and general disarray …was not very friendly. So I’ve written a whole new library with examples, templates, better CSS, better email validation, and better url validation, not to mention code you can really use right off the shelf with a Creative Commons license. Try version 1.0 today and let me know what you think!

Share on Facebook

Easier PHP forms, part 3

Monday, July 6th, 2009

In Part 1 I showed you how to simplify building basic forms in PHP.

In Part 2 I extended this to show you almost every input type you’d ever want.  (For the full set, hire me!)

Now in Part 3, let’s add some friendly javascript to make sure your users input valid data.

I shouldn’t need to point out that Javascript isn’t enough.  If, for any reason, javascript doesn’t run then all your tests will be ignored and bad data can be input.  Injection attacks can also circumvent javascript with ease.  There is no way to avoid PHP testing server-side, but javascript is friendly to the average user and saves your server bandwidth.

The first thing we’ll need is to identify where we’ll need to add tests.

  • make sure this field isn’t empty
  • urls in the right format
  • emails in the right format
  • select must be/must not be a certain value
  • custom tests

In every case we’ll need some javascript in the <head> section of the page. Something like…

function test_form_X() {  // where X is the name of a form, in case there are multiple forms on the same page
  ok=true;
  msg='';
  first=1;
  $('#X .form_item').removeClass('error');  // jQuery/CSS magic!
 
  ...
  // let's assume we have a form element called form_element_N.
  if(is_form_element_N_ok()==false) {
    ok=false;
    msg+='Form element N is no good!\n';
    if(first) {
      form_element_N.focus();
      first=0;
    }
    // every input/select/textarea is inside an input_item inside a form_item.
    $('#form_element_N').parent.parent.addClass('error');  // jQuery/CSS magic!
  }
  ...
 
  if(ok==false) alert(msg);
  return ok;
}

The trick is twofold: knowing which is_form_element_N_ok() to put in.  First, I take that whole inner part of the test and put it in a function by itself.

function add_form_error($name,$message) {
  $str ="      if(first) {\n";
  $str.="        first=0;\n";
  $str.="        \$('#$name').focus();\n";
  $str.="      }\n";
  $str.="      \$('#$name').parent.parent.addClass('error');\n";
  $str.="      ok=false;\n";
  $str.="      msg+='".str_replace(array("'","\'"),$message)."';\n";
  return $str;
}

so let’s create our first test. We’ll have to update create_form_start().

// Create a javascript error message for fields that can't be left blank.
function add_form_test_required($name,$label=null) {
  $test="    if(\$('$name').value==null || \$('$name').value.length==0) {\n"
       .add_form_error($name,"$label is required.\n")
       ."    }\n";
 
  add_form_test($test,$name);
}
 
function add_form_test($test,$name) {
  global $forms_to_validate,$last_form_name;
 
  $forms_to_validate[$last_form_name].=$test;
}
 
function create_form_start(name='form1',$classname='form',$action='',$method='post',$target='') {
  global $forms_to_validate,$last_form_name;
 
  $forms_to_validate[$name]='';
  $last_form_name=$name;
  return "
&lt; form id="$name" class="$classname" action="$action" enctype="multipart/form-data" method="$method"&gt;\n";
}

So where do these tests go? Well, when we’re ready to echo the page, we call the following method

function form_header() {
  global $forms_to_validate;
 
  echo "  <script type="'text/javascript'"><!--mce:0--></script>\n";
}

So what does this mean? It means we can have our forms built in a few lines of code.

$body=create_form_start();
$body.=create_form_text('name','Name','','Your Name');
$body.=create_form_password('user_pass','Password','','Your Password. <a href="forgot.php">Did you forget your password?</a>');
$body.=create_form_required('name','Name');

There is nothing stopping us from writing a test inside create_form_password that checks the password is a certain length, or from adding a “confirm password” field and checking they are both the same. This same technique can be extended in any way you please.

As an interesting aside, HTML5 supports URLs and emails natively – the tests are built right into the web browser. No need for javascript!

Share on Facebook

canvas library, game improvements

Friday, July 3rd, 2009

Colleen’s game got slower and slower the longer you played it.  I found the leak and now it runs at a consistent speed.  Both games have improved clock calculations – I’m using setTimeout(variable) instead of setInterval(static).  The framerate is still garbage but I hope that’s just because canvas is still so new they haven’t figured out how to make it run fast.

Yes, that’s right… it’s Mozilla’s problem… my code is puuuuurfeeect…. :P

Share on Facebook

Firefox canvas javascript oddities

Thursday, July 2nd, 2009

I’m running a simple animation at 30fps in game. I have noticed several irregularities.

Somewhere in the pipeline the program is allocating and then freeing as much as 100mb at runtime, over and over, despite my attempts to minimize all memory thrashing. local variables are the only things being created/destroyed once the game has begun and none of them are variable type objects (except for some printed strings)

Somewhere in the pipeline the Firefox browser would rather drop frames (not update the canvas) than slow down its calculations. Given that I am not informed of this decision I can’t tell my program to stop calculating how to draw the picture. I have personally seen this lead to timeout warnings on long operations.

What does this mean? Avoid doing a lot of calculations to update your canvas. Avoid memory thrashing as much as possible. If you’re going to make a game make it a turn-based game with very few animations OR make it a very simple game with few moving parts. I wanted to make a Robotron/SmashTV/Mutant Storm kind of game but that’s a no go for now. 3D rendering? A couple orders of magnitude faster and then we might be ready for DOOM.

Share on Facebook

HTML 5 Canvas Colleen’s Game (bubble bobble/snood clone)

Thursday, July 2nd, 2009

Did you install Firefox 3.5? Then you can play Colleen’s Game right now, written by yours truly.  In the near future you’ll find more Canvas samples right here.

This game was considerably more work – it took almost 3 times as long to get working.  Javascript thrashes your RAM like crazy so don’t be surprised if you see some unexpected slow downs.

Share on Facebook

HTML 5 Canvas Asteroids

Wednesday, July 1st, 2009

Did you install Firefox 3.5? Then you can play Asteroids right now, written by yours truly.  In the near future you’ll find more Canvas samples right here.

Share on Facebook

Easier PHP forms, part 2

Tuesday, June 30th, 2009

In part 1 I showed you my method for simplifying form building and maintenance.  Now I’d like to show you a few of the things that can be simplified by using this system.  Included are:

  • prices
  • passwords
  • emails
  • textareas
  • selects
  • yes/no (booleans)
  • files
  • hiddens
  • cancel buttons
  • submit buttons
  • “are you sure you want to do that?” submit buttons
  • addresses

I use this code for 95% of the forms I build and then modify the CSS to get the effects a customer wants.

In part 3 I’ll show you how to use jQuery to automatically validate user data.

//------------------------------------------------------------------------------
function create_form_price($name,$value=null,$label=null,$hint=null) {
  return create_form_row("\$&lt;input type='text' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."'&gt;",$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_password($name,$value=null,$label=null,$hint=null) {
  return create_form_row("&lt;input type='password' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."'&gt;",$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_password_confirm($name,$value=null,$label=null,$hint=null) {
  add_form_test_password_confirm("pass",$label);  // we'll cover this in part 3
 
  return create_form_row("&lt;input type='password' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."'&gt;",$label,$name,"").
  create_form_row("&lt;input type='password' name='{$name}_confirm' value=''&gt;","&amp;nbsp;",$name."_confirm",$hint);
}
 
//------------------------------------------------------------------------------
function create_form_email($name,$value=null,$label=null,$hint=null) {
 // @TODO: add email validation.
 $test="    regex_{$name}=/^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*(\.[a-zA-Z]{2,4})$/\n"
 ."    compare_to=f.{$name}.value.replace(/^\s+|\s+$/g,'');\n"
 ."    if(compare_to!='' &amp;&amp; !compare_to.match(regex_{$name})) {\n"
 .add_form_error($name,"$label does not appear to be a valid email.\n")
 ."    }\n";
 add_form_test($test,$name);
 
 return create_form_row("&lt;input type='text' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."'&gt;",$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_textarea($name,$value=null,$label=null,$hint=null) {
 return create_form_row("&lt;textarea name='$name' rows='10' cols='80'&gt;".htmlspecialchars($value,ENT_QUOTES)."&lt;/textarea&gt;",$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_select_internal($name,$options,$value=null,$multiple=0,$extra='') {
 $sel="&lt;select id='$name' name='$name' $extra";
 if($multiple&gt;0) {
 $sel.=" multiple='yes' size='$multiple'";
 }
 $sel.="&gt;\n";
 if(is_array($options)) {
 foreach($options as $k=&gt;$v) {
 $k=htmlspecialchars($k,ENT_QUOTES);
 $v=htmlspecialchars($v,ENT_QUOTES);
 $sel.="  &lt;option value='$k'&gt;$v&lt;/option&gt;\n";
 }
 }
 $sel.="&lt;/select&gt;";
 $v=isset($value)?htmlspecialchars($value,ENT_QUOTES):"";
 return str_replace("value='$v'","value='$v' selected",$sel);
}
 
//------------------------------------------------------------------------------
function create_form_select($name,$options,$value=null,$label=null,$hint=null,$multiple=0,$extra='') {
 $sel=create_form_select_internal($name,$options,$value,$multiple,$extra);
 return create_form_row($sel,$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_bool($name,$value=null,$label=null,$hint=null,$extra='') {
 $sel="&lt;select name='$name' $extra&gt;\n";
 $sel.="  &lt;option value='yes'&gt;"._t("Yes")."&lt;/option&gt;\n";
 $sel.="  &lt;option value='no'&gt;"._t("No")."&lt;/option&gt;\n";
 $sel.="&lt;/select&gt;";
 $v=isset($value)?htmlspecialchars($value,ENT_QUOTES):"";
 $sel=str_replace("value='$v'","value='$v' selected",$sel);
 return create_form_row($sel,$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_file($name,$label='',$hint='') {
 $inner="&lt;input name='$name' type='file'&gt;";
 
 return create_form_row($inner,$label,$name,$hint);
}
 
//------------------------------------------------------------------------------
function create_form_address($name,$value=null,$label=null,$hint=null) {
 if(!isset($value['address1'])) $value['address1']="";
 if(!isset($value['address2'])) $value['address2']="";
 if(!isset($value['city'])) $value['city']="";
 if(!isset($value['region'])) $value['region']="";
 if(!isset($value['country'])) $value['country']="";
 if(!isset($value['postalcode'])) $value['postalcode']="";
 
 $str =create_form_text($name."_address1",$value['address1'],"Street");
 $str.=create_form_text($name."_address2",$value['address2'],"");
 $str.=create_form_text($name."_city",$value['city'],"City");
 $str.=create_form_text($name."_region",$value['region'],"State/Province");
 $str.=create_form_text($name."_country",$value['country'],"Country");
 $str.=create_form_text($name."_postalcode",$value['postalcode'],"Postal Code");
 
 return create_form_row($str,$label,null,null);
}
 
//------------------------------------------------------------------------------
function create_form_hidden($name,$value=null) {
 return "  &lt;input type='hidden' id='$name' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."'&gt;\n";
}
 
//------------------------------------------------------------------------------
function create_form_submit($name,$value,$extra='') {
 global $last_form_name;  // we'll cover this is in part 3
 
 return "  &lt;input type='submit' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."' onclick='javascript:return validate_$last_form_name(this.form);' $extra&gt;\n";
}
 
//------------------------------------------------------------------------------
function create_form_cancel($name,$value,$redirect) {
 return "  &lt;input type='button' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."' onclick='javascript:window.location=\"".htmlspecialchars($redirect,ENT_QUOTES)."\";'&gt;\n";
}
 
//------------------------------------------------------------------------------
function create_form_submit_confirm($name,$value,$confirm="Are you sure?",$validate=0) {
 global $last_form_name;  // we'll cover this is in part 3
 
 $str="  &lt;input type='submit' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."' onclick='javascript:return (confirm(\"".str_replace(array("'","\n"),array("\'","\\n"),$confirm)."\")";
 if($validate!=0) {
 $str.=" &amp; validate_$last_form_name(this.form)";
 }
 $str.=");'&gt;\n";
 return $str;
}
Share on Facebook

Easier PHP forms, part 1

Monday, June 29th, 2009

Building and maintaining forms in PHP is one of the most time-consuming parts of the job.  Put another way, it’s one of the biggest bottlenecks for fast prototyping and development.  Getting rid of bottlenecks speeds up work and increases overall joy.  So how do we do this?

The first thing I did was look at all the forms I had been writing.  In general they all follow a pattern:

<DOCTYPE ...>
<html ...>
<head>
  ...
  // javascript tests to make sure user fills form correctly
</head>
<body>
...
<form name='blah' action='#' method='post'>
...

  <div class='form_item'>
    <label for='ABC'>A human-readable name for the input</label>
    <div class='input_item'>
      <input name='ABC'>  <?/* or select/textarea/etc */?>
      <div class='help'>Some explanation text here.</div>
    </div>
    <div class='clear'></div>
  </div>
...
</form>
...
</body>
</html>

It seemed that cutting and pasting this was rife with errors.  Even worse is trying to change the layout of the page – a major (non-CSS) alteration could really ruin my day!  I had to come up with somethin better.

function create_form_row($input,$label_name=null,$label_for=null,$hint=null) {
                         $str ="  <div class='form_item'>\n";
  if(isset($label_name)) $str.="    <label for='$label_for'>$label_name</label>\n";
                         $str.= "    <div class='input_item'>\n";
                         $str.= "      $input\n";
  if(isset($hint))       $str.= "      <div class='help'>$hint</div>\n";
                         $str.= "    </div>\n";
                         $str.= "    <div class='clear'></div>\n";
                         $str.= "  </div>\n";
  return $str;
}

function create_form_start($name,$classname='form',$action='',$method='post',$target='') {
  return "<form enctype='multipart/form-data' id='$name' name='$name' class='$classname' action='$action' method='$method'>\n";
}

function create_form_end() {
  echo '</form>\n';
}

At first this might seem like a lot of work for not much gain.  Think of this as the center of the onion.  If we build layers on top of it we start to see some big big benefits.

function create_form_check_inner($name,$value=null,$checked=false) {
  return "<input type='checkbox' id='$name' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."'".($checked?" checked":"").">";
}

//------------------------------------------------------------------------------
function create_form_check($name,$value=null,$label=null,$hint=null,$checked=false) {
  return create_form_row($label,create_form_check_inner($name,$value,$checked),$name,$hint);
}

//------------------------------------------------------------------------------
function create_form_text_inner($name,$value=null,$extra='') {
  return "<input type='text' id='$name' name='$name' value='".htmlspecialchars($value,ENT_QUOTES)."' $extra>";
}

//------------------------------------------------------------------------------
function create_form_text($name,$value=null,$label=null,$hint=null,$extra='') {
  return create_form_row(create_form_text_inner($name,$value,$extra),$label,$name,$hint);
}

So now when I want to create a new form all I do is

echo create_form_start('form1');
echo create_form_text('user_name','','User Name','what is your name?');
echo create_form_check('love_this','yes','Love','Do you love this new form system?',true);
echo create_form_end();

In Part II I’ll show you how to use create_form_ elements for passwords, selects, textareas, and more.

In Part III I’ll show you how you can use this system and jQuery to add checks that make sure users put in valid emails, required fields, and so on.

Share on Facebook

Updating PHP cookies without breaking the experience

Friday, June 19th, 2009

PHP cookies are a great idea for storing data temporarily.  Sure, they have their security problems but (arguably) they do more good than bad.  The trickiest part about cookies is when you update your PHP and it forces you to change the contents of your cookies.  You don’t want your faithful site visitors to see an error message!  Consider the following naive implementation:

function save() {
  setcookie("username","foo",60*60*24*30);
}
 
function load() {
  return isset($_COOKIE["username"])?$_COOKIE["username"]:'';
}

It doesn’t take a lot of imagination to see how this could break.  Let’s start by making it easier to store lots of info in a single cookie.

function save() {
  $data=array('username'=&gt;'foo', 'something else'=&gt;'bar');
  $str=serialize($data);
  setcookie("userdata",$str,60*60*24*30);
}
 
function load() {
  $data=array('username'=&gt;'','something else'=&gt;'');
  if(isset($_COOKIE['userdata'])) {
     $data=unserialize($_COOKIE['userdata']);
  }
  return $data;
}

Ok, but what about if we need to add more data to our cookies later?  Everyone with an old cookie will be missing parts of $data and we’ll have to catch that.

function save() {
  $data=array('version'='1.0', 'username'=&gt;'foo', 'something else'=&gt;'bar');
  $str=serialize($data);
  setcookie("userdata",$str,60*60*24*30);
}
 
function load() {
  $data=array('username'=&gt;'','something else'=&gt;'');
  $old=false;
  if(isset($_COOKIE['userdata'])) {
    try {
      $data=unserialize($_COOKIE['userdata']);
    }
    catch(Exception $e) {
      $old=true;  // this version is so old &amp; busted we can't read it at all.
    }
    if(!$old) {
      // Use the $data['version'] to help us figure out what pieces of $data need to be filled in.
    }
  }
  return $data;
}

And… voila!

Share on Facebook