SitePoint Sponsor

User Tag List

Results 1 to 6 of 6
  1. #1
    SitePoint Addict bronze trophy
    Join Date
    Apr 2013
    Location
    Ithaca
    Posts
    351
    Mentioned
    6 Post(s)
    Tagged
    1 Thread(s)

    [PHP] The PHP GUI API: Create HTML GUI Renderable Objects programatically

    Just realized that this forum has been added to SitePoint, I remember I made a suggestion for code critiques/review forum at one point so glad it came through. I'd like to contribute to this subforum as much as I can, as I do have some packages/API I used to write but yet to receive any comments/critiques from expert coders. Here's one of them, which I call the PHP GUI API.

    What this API does is to create HTML objects such as Button, TextField, CheckBox, Form, Table, iFrame etc. The top level abstract class is simply called GUI, which specifies attributes and methods available for all GUI objects. Below the base GUI class are the Component and Element classes, both are abstract. The Component class is a supertype for all standard GUI objects such as Button, TextField, and Image, while the Element class contains attribute objects such as Align, Color and Font. There is also a Container abstract class that extends Component class, it is useful for container type objects like Form, Table and DropdownList. I also create some very special container classes for handling HTML paragraphs, div and article/document. All these objects form the foundation of the GUI API.

    The most interesting part is the Render objects which stores a reference of the GUI objects and renders their attributes into HTML compatible strings ready to be displayed on the browser. Since PHP does not allow inner/nested classes, the renderer classes are completely separated from the GUI classes. It may not be a bad thing though, as decoupling renderers from GUI objects may provide flexibility to compensate for changes in HTML syntax in future without modifying the core GUI objects. Although the renderer object do a lot of things behind the scene, as client coder all you have to do is to call the method render() on the outer-most GUI objects(it can be a table, a form or an article) and the renderers will render the entire object graph nicely. With these, You can create object oriented view in your application, in a way similar to Java Swing, but the PHP GUI API is specialized for web/HTML-based applications rather than desktop apps.

    This library was actually written almost a year ago, the current GUI API in my own application has been modified to use the PHP Collection Frameworks(aka ArrayList, HashMap...) rather than PHP Native Arrays and ArrayObjects. Nonetheless, I believe it's better to show the older version of my PHP GUI API as it does not introduce dependency on the Collection package and other related libraries from my framework.



    What do you think about the GUI API? Any comments on the architecture design, and suggestions for its future improvement? The package is hosted on phpclasses.org, and the link is provided below:
    http://www.phpclasses.org/package/78...ogrammers.html

  2. #2
    SitePoint Addict bronze trophy
    Join Date
    Apr 2013
    Location
    Ithaca
    Posts
    351
    Mentioned
    6 Post(s)
    Tagged
    1 Thread(s)
    Here is an example of using PHP GUI API to produce a nicely looking Registration Page:

    PHP Code:
    <?PHP
    if($_POST['submit']){
        echo 
    "Your Username is: {$_POST['username']}";
        echo 
    "<br>";
        echo 
    "Your Password is: {$_POST['password']}";
        echo 
    "<br>";
        echo 
    "Your Confirmed Password is: {$_POST['password2']}";
        echo 
    "<br>";
        echo 
    "Your Email is: {$_POST['email']}";
        echo 
    "<br>";
        echo 
    "Your Confirmed Email is: {$_POST['email2']}";
        echo 
    "<br>";
        echo 
    "Your Gender is: {$_POST['gender']}";
        echo 
    "<br>";
        echo 
    "Your Country is: {$_POST['country']}";
        echo 
    "<br>";
        if(empty(
    $_POST['username'])) echo "<b>Error: You have not entered a username, please go back and fill in the blank.</b>";
        elseif(empty(
    $_POST['password']) or $_POST['password'] != $_POST['password2']) echo "<b>Error: You have not entered a password, or you have not confirmed it successfully.</b>";
        elseif(empty(
    $_POST['email']) or $_POST['email'] != $_POST['email2']) echo "<b>Error: You have not entered an email, or you have not confirmed it successfully.</b>";
        else echo 
    "<b>You have registered successfully!</b>";
        return;
    }

    echo 
    "<center><b><u>Registration Form</u></b></center>";
    $form = new Form("myform");
    $form->setAction("test.php");
    $form->setMethod("post");
    $form->setAlign(new Align("center""middle"));

    $src = new URL("http://www.tivo.com/assets/images/abouttivo/resources/downloads/backgrounds/Green_bkgd_72rgb.jpg");
    $field = new FieldSet();
    $field->setLineBreak(FALSE);
    $field->setBackground(new Image($src"back"300));

    $field->add(new Legend("Required Field"));
    $field->add(new Division(new Comment("Please enter your username here, it must be alphanumeric."), "username"));
    $field->add(new TextField("username""admin"10));
    $field->add(new Paragraph(new Comment("Please fill in your password confirmed password here.")));
    $field->add(new PasswordField("password""password""123456"));
    $field->add(new PasswordField("password""password2"));

    $email = new Paragraph();
    $email->setID("email");
    $email->setFont(new Font(14"Times New Roman"));
    $email->getFont()->setWeight("bolder");
    $email->setForeground(new Color("#000080"));

    $email->add(new Comment("Please type your email and confirmed email here."TRUE));
    $email->add(new PasswordField("email""email"));
    $email->add(new PasswordField("email""email2"));
    $field->add($email);

    $field2 = new FieldSet(new Legend("Additional Fields"));
    $field2->setBackground(new Color("red"));

    $radiolist = new RadioList("gender");
    $radiolist->add(new RadioButton("Male""gender""male"));
    $radiolist->add(new RadioButton("Female""gender""female"));
    $radiolist->add(new RadioButton("Unknown""gender""unknown"));
    $radiolist->check("unknown");

    $countries = array("Britain""France""Germany""Italy""Spain""America""Canada""Russia""Australia");
    $alias = array("gbr""fra""ger""ita""esp""usa""can""rus""aus");
    $default "usa";
    $dropdown = new DropdownList("country");
    $dropdown->fill($countries$alias$default);

    $comment2 = new Comment("Your Citizenship: "FALSE);
    $comment2->setBold(TRUE);
    $comment2->setUnderlined(TRUE);
    $comment2->setForeground(new Color("yellow"));

    $field2->add(new Comment("Your Gender: "FALSE));
    $field2->add($radiolist);
    $field2->add($comment2);
    $field2->add($dropdown);

    $submit = new Button("Register""submit""submit");
    $submit->setLineBreak(FALSE);

    $form->add($field);
    $form->add($field2);
    $form->add(new Image(new URL("../templates/icons/facebook.gif"), "facebook"20));
    $form->add($submit);
    $form->add(new Image(new URL("../templates/icons/twitter.gif"), "twitter"20));
    echo 
    $form->render();
    ?>
    Screenshot preview:

  3. #3
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,278
    Mentioned
    18 Post(s)
    Tagged
    0 Thread(s)
    My thoughts:

    - First, don't host your code on phpclasses. It requires the rest of us to register just to get a look at your code. That deterred me for a while. Ultimately I used BugMeNot.

    - It would be nice if your class names correlated with their file paths. Otherwise I have to go hunting for each class file, which isn't fun.

    - Consider using namespaces. For example, HallOfFamer\GUI\Renderer\DocumentRenderer

    - It seems like you tried to de-couple the rendering from the document representation, which is good, but you can probably de-couple it a bit further. Elements probably shouldn't be instantiating renderers in their constructor. They probably shouldn't even be aware of renderers at all. Likewise, renderers shouldn't be concerned with document representation. They shouldn't have any tag names or attributes hard coded into them.

    Here's some pseudo-code to more concretely show what I mean.

    First up: document representation. I would probably mimic the W3C's document object model.

    Code php:
    /*
    Represents Paragraphs
    */
    class HtmlParagraphElement extends HtmlElement
    {
        /*
           attribute DOMString        align;
        */
    }
     
    /*
    Represents All HTML elements.
    */
    abstract class HtmlElement extends Element
    {
        /*
           attribute DOMString        id;
           attribute DOMString        title;
           attribute DOMString        lang;
           attribute DOMString        dir;
           attribute DOMString        className;
        */
    }
     
    /*
    Represents an element in an HTML or XML document.
    */
    abstract class Element extends Node
    {
        /*
              readonly attribute DOMString        tagName;
              DOMString          getAttribute(in DOMString name);
              void               setAttribute(in DOMString name, 
                                              in DOMString value)
                                                    raises(DOMException);
              void               removeAttribute(in DOMString name)
                                                    raises(DOMException);
              Attr               getAttributeNode(in DOMString name);
              Attr               setAttributeNode(in Attr newAttr)
                                                    raises(DOMException);
              Attr               removeAttributeNode(in Attr oldAttr)
                                                    raises(DOMException);
              NodeList           getElementsByTagName(in DOMString name);
              void               normalize();
        */
    }
     
    /*
    Represents a single node in the document tree.
    */
    abstract class Node
    {
        /*
          // NodeType
          const unsigned short      ELEMENT_NODE                   = 1;
          const unsigned short      ATTRIBUTE_NODE                 = 2;
          const unsigned short      TEXT_NODE                      = 3;
          const unsigned short      CDATA_SECTION_NODE             = 4;
          const unsigned short      ENTITY_REFERENCE_NODE          = 5;
          const unsigned short      ENTITY_NODE                    = 6;
          const unsigned short      PROCESSING_INSTRUCTION_NODE    = 7;
          const unsigned short      COMMENT_NODE                   = 8;
          const unsigned short      DOCUMENT_NODE                  = 9;
          const unsigned short      DOCUMENT_TYPE_NODE             = 10;
          const unsigned short      DOCUMENT_FRAGMENT_NODE         = 11;
          const unsigned short      NOTATION_NODE                  = 12;
     
          readonly attribute DOMString        nodeName;
                   attribute DOMString        nodeValue;
                                                // raises(DOMException) on setting
                                                // raises(DOMException) on retrieval
     
          readonly attribute unsigned short   nodeType;
          readonly attribute Node             parentNode;
          readonly attribute NodeList         childNodes;
          readonly attribute Node             firstChild;
          readonly attribute Node             lastChild;
          readonly attribute Node             previousSibling;
          readonly attribute Node             nextSibling;
          readonly attribute NamedNodeMap     attributes;
          readonly attribute Document         ownerDocument;
          Node               insertBefore(in Node newChild, 
                                          in Node refChild)
                                                raises(DOMException);
          Node               replaceChild(in Node newChild, 
                                          in Node oldChild)
                                                raises(DOMException);
          Node               removeChild(in Node oldChild)
                                                raises(DOMException);
          Node               appendChild(in Node newChild)
                                                raises(DOMException);
          boolean            hasChildNodes();
          Node               cloneNode(in boolean deep)
                                                raises(DOMException);
        */
    }

    Next up: rendering. The big benefit of decoupling the renderer from the document is that we can apply one of possibly many different renderers. We might have a legacy HTML renderer, which would render uppercase tag names, no attribute quotes, no closing tags when optional, and so forth. Then we could also have a modern HTML renderer, which would render lowercase tag names, always quoted attributes, and always closing tags. Then we could also have an HTML-compatible XHTML renderer, which would render self-closing slashes and CDATA blocks. And possibly many more kinds of renderers.

    This hypothetical library might work like this:

    Code PHP:
    $paragraph = new HtmlParagraphElement();
    $paragraph->setAttribute('align', 'left');
     
    $text = new Text();
    $text->data = 'Hello, World!';
     
    $paragraph->appendChild($text);
     
    $img = new HtmlImageElement();
    $img->setAttribute('src', 'photo.jpg');
    $img->setAttribute('alt', 'Alternate photo text');
     
    $paragraph->appendChild($img);
     
    $renderer = new LegacyHtmlRenderer();
    $renderer->render($paragraph); // <P align=left>Hello, World!<img src=photo.jpg alt="Alternate photo text">
     
    $renderer = new ModernHtmlRenderer();
    $renderer->render($paragraph); // <p align="left">Hello, World!<img src="photo.jpg" alt="Alternate photo text"></p>
     
    $renderer = new HtmlCompatibleXhtmlRenderer();
    $renderer->render($paragraph); // <p align="left">Hello, World!<img src="photo.jpg" alt="Alternate photo text" /></p>
    "First make it work. Then make it better."

  4. #4
    SitePoint Addict bronze trophy
    Join Date
    Apr 2013
    Location
    Ithaca
    Posts
    351
    Mentioned
    6 Post(s)
    Tagged
    1 Thread(s)
    Thank you for your comment, and yeah I agree there are problems with PHPclasses.org as it requires registration/logging-in to view the files. I was planning to use GitHub or SourceForge but didnt know how to use them, since I never had experience with Linux commands.

    The newest version indeed uses namespace, and the class fully qualified names match file structure if converted to lower-case. For instance, the class Mysidia\Resource\GUI\Container will resolve to ../site/resource/gui/container.php, the outermost namespace Mysidia is the identifier for my projects. I did not showcase the new version since it is coupled to another package I made called PHP Collection Framework, which means you have to browse through them both to understand how the system works.


    I do try to decouple the renderer from the GUI components/containers in a way that future upgrades/modifications will be easier and more straightforward. You brought up a very nice suggestion, tbh I am not really quite sure of the design of GUI Elements as initially they were designed as subclasses of GUI Components(aka TextField, Button, RadioButton). At one point I made a decision to separate Element type classes from the standard GUI Components, but it may still be insufficient. I will definitely look into improving the design of GUI Elements further, I appreciate your advices.

    I agree that the renderer classes should not be aware of HTML tag names, guess it's indeed a flaw in the design. These tags should've been designed in the GUI Components/Containers classes, and the renderers are supposed to call getTag() to retrieve these values rather than hard coding them based on Component types. This will be changed in next version too, thanks for the suggestion.

    And come to think about it, you seem to prefer the idea of a tree-like HTML structure similar to XML, in which you have methods like appendChild(), setAttributes(). For me, this library is designed more like Java Swing, in which the components and containers are created and manipulated just like Java GUI API. Not sure which approach is better though, or does it really matter?

  5. #5
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Thanks Jeff,

    You hit the points I wanted to but was trying to find time

  6. #6
    SitePoint Wizard silver trophybronze trophy asp_funda's Avatar
    Join Date
    Jun 2003
    Location
    ether
    Posts
    4,497
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Hall of Famer View Post
    I was planning to use GitHub or SourceForge but didnt know how to use them, since I never had experience with Linux commands.
    You don't need to know command line usage (better if you do but not mandatory). Github has a GUI tool for Windows & Mac as well, so you can just create a repo on Github, use their tool to clone it on your local, add code & push it. Or you can install other GUI tools. I use TortoiseGit on Windows & SmartGit on Mac - both are free.
    Our lives teach us who we are.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Me - Photo Blog - Personal Blog - Dev Blog
    iG:Syntax Hiliter -- Colourize your code in WordPress!!


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •