Thursday, October 31, 2013

Form resubmission

Everyone who writes online forms hates the back button and the refresh button. All kinds of solutions are out there, from ajax submissions (with their annoying scalability delays) to disabling buttons which work intermittently, to using POST requests and giving the user a warning (such as most banks do) leaving the user clueless as to what they have done wrong.

There is one, and only one correct way.

Each html form presented to the user needs to have a unique key associated with it.
When this form arrives with the server, the controller needs to see if it has processed the unique form before. If it has, then it should present the user with the same form again, otherwise it needs to process the form.
require_once("../includes/include.php");
$t = @$_REQUEST['t'];
$s = @$_SESSION['lastreq'];
if (!isset($s[$t])) {
 // Do the work.
 $_SESSION['lastreq'][$t] = true ;
}
?>
form
input type=text name=tname value=
input type=hidden name=t value=t
input type=submit
/form

Here's a simplified controller model:
The user will still get a warning when submitting 'post' type forms, but if they decide to continue, they will get a meaningful message from the server.

Saturday, September 5, 2009

Vector Drawing in a browser

Inevitably, the apps project is going to have BOMS, or builds, or a product hierarchy.
 
A hierarchy already exists inasfar as products belong to product groups, which in turn belong to ranges. A build is slightly different because a product does not need to belong only to one group.
 
The definition of a products 'belonging' then does not come from the product itself, but from its parent. i.e. you create a parent product, and then decide what products belong to it. Some of those products may themselves be builds and so on.
 
If you haven't come across the concept before, then it is difficult to get your head around without a picture, which is where the subject of this blog comes in.
 
Browsers are notoriously difficult to get dynamic, vector pictures into. The problem has been addressed with flash, java applets and front end applications. However there is another answer : clever javascript libraries.
 
Include a small 7k library from http://www.walterzorn.com/jsgraphics/jsgraphics_e.htm, then call the drawing elements of the library from java like this:
 
function myDrawFunction() {
  for (i = 10; i < 259 ; i = i + 100) {
   jg_doc.setColor("#"+genHex());
   jg_doc.drawRect(300-i,300-i,i*2,i*2)
   jg_doc.drawString("Some Text",300-i,300-i);
  }
  jg_doc.paint(); // draws, in this case, directly into the document
}
 
var jg_doc = new jsGraphics(); // draw directly into document
It works ! It is cross browser and fast !

Monday, May 18, 2009

PHP and variable scope

For some historical reason, ostensibly to do with the design philosophy, PHP
can store data in the user session, but can't store data with application
scope.

ASPs can do it, JSPs can do it, but PHP doesn't, and has no plans to any
time in the future.

For this application, it means that every single user is going to have to
grab hold of a copy of the database schema every time they start their
session. When we reach the point of tens of thousands of hits per second, we
are going to need far more power in the database than would otherwise be the
case. As it happens, this particular application is designed such that users
of a particular size are going to be happy to pay for their own, uncontended
database instance, and no single company is ever going to grow to a size
where they would need anything more.

It is the principle which counts, though.

You could simply write stuff to disk, and put up with the performance
overhead, but then you might as well use a federated or replicated MySQL
instance. A better solution is to use Memcached. It is a highly scaleable
cacheing tool with a handy PHP interface called Memcache. Install the
package, start the daemon, and throw objects at it. For private objects, use
a name prepended to the session id. For public objects like the schema, just
use a common name. Although it isn't a straightforward install, we
essentially have application scope objects.

To use the memcache interface, I would need to rewrite some code. The code
is going to first check for object existence in memcache, then read from DB
and put into memcache if it isn't already there. I probably wouldn't
normally use the options, but it is possible to put objects into memcache
with a limited time, and to restrict the memory usage of the daemon.

Not something I need to worry about yet (the apps are lightning-fast), but
good to know there's a solution when I need one.

Sunday, May 17, 2009

PHP - Mysql vs Mysqli vs open database conns

There is an ongoing question over what database the application should use,
and indeed whether it should be database independent.

My view on this is that there is pretty much only MySQL. It is free, fast,
scalable, widely used and easy to find support for. Beyond MySQL, there is
Oracle which isn't free, but is more scalable for certain types of
application. Importantly, if it is being used in a site with expertise in
some other database, MySQL really can be left alone, and it will work away
happily without needing maintenance.

Of course MySQL won't be free forever now that Sun own it, and upgrades are
likely to be sporadic with the best features being a cost option.

MySQL also has two distinct php interfaces. MySQLI, and the older MySQL. The
uptake of MySQLI has been relatively poor even though it has been out for a
few years, possibly because there isn't currently an implementation for a
persistent connection.

As a consequence of all this, there are a few design philosophies:

- All database connections are controlled through a single include.php, the
class needs to be extended in order to be used.
- All SQL outside the class needs to be ANSI-99.
- Accomodating a new database should only need a quick whip through the
parent class. An hour or so's work.
- I am using MySQLI as an interface, although it should be a simple matter
to revert to MySQL, or indeed implement memcache within the parent if
necessary.

...job done, and I am not giong to even bother with the complexities of user
configured database connections. In practise, the configuration of DB
connections gives rise to at least 50% of all installation problems anyway.

Friday, May 8, 2009

Quotation Creation

I've spent some time going through all of the bugs in the quotation creation program this week and have arrived at the point where I can't find any more to fix!

The program emails PDFs, or prints from emails, allows for the entry of notes against both the line or header, maintains details of GST numbers etc.

I'm now at the stage where I can go in one of two directions. Either :

- I expand functionality to allow for multi-currency, variable GST rates, complex formatting against notes and optional quote layouts etc.
- I expand breadth to encompass invoicing, ordering, stock control, credit control etc.

Every step in additional functionality is also a direction toward complexity, so I'm going to head down the latter of the two options above.
With an architecture painfully assembled around the quotes, adding new form types should be straightforward. We'll see, as I start on sales orders.

Example of PDF attached.

Thursday, April 30, 2009

Updated quote interface

I've spent some time on the quote interface trying to weigh off a very simple interface (such that first time users aren't turned off) against the flexibility of a system which caters for different types of business.

Oracle, for example, has an interface which is far too complex with too many options. Most online invoicing systems aren't going to be viable most of the time because they are just too simple.

The way I have tried to get around this is with context-sensitive tabs. You see the basics by default, but you can click a tab to see more. When you are editing a quote header, there is one set of tabs, and when you are editing the lines, the tabs change.

All best explained in the video below.

Thursday, April 23, 2009

Making PDFs using PHP

Having spent several days using a HTML->PDF conversion program (DOMPDF), I've become disappointed with the original conversion software. Part of the reason was the the code was abstracted beyond belief. Every line of code seemed to make a reference to a class whose name was calculated then dynamically loaded; every function referred to another function in the same class which contained only one line setting a protected object in the class.
 
The code was also unneccessarily padded out with comments like the one below making a thousand lines into five thousand:
 

  /**
   * a record of the current font
   */
  public  $currentFont = '';
 
and here's the bit which really upset me:
 
function DOMPDF_autoload($class) {
  $filename = mb_strtolower($class) . ".cls.php";
  require_once(DOMPDF_INC_DIR . "/$filename");
}
 
if ( !function_exists("__autoload") ) {
  /**
   * Default __autoload() function
   *
   * @param string $class
   */
  function __autoload($class) {
    DOMPDF_autoload($class);
  }
}
 
What the above code does is to hunt through an (abstracted) directory for a file of a particular description whenever a class or function is called which isn't in the already loaded code. Not only that, but the whole thing has been abstracted to a further external function. Try debugging that !
 
So in summary, I couldn't really tell what was going on. More importantly, the code has some serious security vulnerabilities, didn't convert images accurately, and was unnecessarily slow. In fact, the DOMPDF code represents everything I hate about obsessively written abstracted software.
 
Predictably, the set of classes didn't actually create PDFS. It was itself an abstraction of a beautiful set of code written right here in New Zealand, which also has a concisely written manual. http://www.ros.co.nz/pdf/readme.pdf. Like all good code, it isn't loosely coupled, everything works the way you expect first time, and you can quickly find out what is going on.
 
To run it, you load a single file containing a single class, instantiate the class and call the methods laid out in the manual. Like this:
 
require_once("class.ezpdf.php");
$pdf =& new Cezpdf();
 
Add an image like this:
 
$pdf->addJpegFromFile('COLDHOT2.JPG',150,650,100);
Add a table like this:
 
$dblink = getconn();
$results = "";
$stmt = "select itemid,name from gst";
$result = mysqli_query($dblink,$stmt);
if ($result) {
 while($row = mysqli_fetch_array($result,MYSQLI_ASSOC)){
  $results[]=$row;
 }
}
$data = $results;
mysqli_close($dblink);
$pdf->ezTable($data,'',$title,$opts);
Add a block of text on the page like this:
 
$opts = array('shaded'=>0);$opts['showHeadings'] = 0;
$opts['yPos'] = 400; $opts['xPos'] = 200;
$opts['fontSize'] = 11; $opts['showLines'] = 0;
$data = array(array('aaa'=>"Is this a\nmulti line problem"));
$pdf->ezTable($data,'',$title,$opts);
File it to disk like this:
 
$pdfcode = $pdf->ezOutput();
$fp=fopen('findthis.pdf','wb');
fwrite($fp,$pdfcode);
fclose($fp);

Or push it to the browser like this:
 
$pdf->ezStream();
The big problem is that I now have two output types. Firstly, an html representation of a document, and secondly a PDF representation. It is certainly possible that the two will diverge over time so that what is printed on the user's printer is different from what is PDF'ed up and sent to a trading partner. In my opinion, this is a small price to pay for a much faster, more reliable, safer and better looking result. DOMPDF is now officially scrapped!