Creating an XForms-based logo generator

Scalable Vector Graphics (SVG) provides an easy way to declaratively create an image using XML, and XForms provides an easy way to edit XML. In this article you will put the two together to create an XForms-based XVG editor for creating SVG images such as logos.

Share:

Nicholas Chase (ibmquestions@nicholaschase.com), Freelance writer, Backstop Media

Nicholas Chase has been involved in Web site development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. Nick has been a high school physics teacher, a low-level radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, an Oracle instructor, and the Chief Technology Officer of an interactive communications company. He is the author of several books, including XML Primer Plus (Sams).



20 February 2007

Introduction

Although you might imagine that the editing of an XForms form and having an SVG image in the browser window update automatically, it is not that easy. But that doesn't mean it can't be done. This article shows you how to create an XForms-based logo generator using SVG.

In order to get the most out of this article, you should be familiar with the basics of XForms. Familiarity with SVG also helps, but isn't crucial.

The code in this article has been tested with Firefox and the Mozilla XForms extension (see Resources to download), but the concepts should apply to any XForms implementation.


What you're trying to accomplish

Let's start by looking at what the finished product looks like. Ultimately, you want a form that enables you to edit the properties of existing shapes, or to remove those shapes altogether. You also want to be able to add new shapes, edit those shapes, and save the data to a logo file. Once you've done that, you want to be able to see the changes right there on the page. Your final logo generator looks something like Figure 1.

Figure 1. The final logo generator
The final logo generator

To accomplish all of this, you will need to use several techniques, including inserting and deleting elements, copying elements from one instance to another, and dynamically updating the contents of an iframe.


A logo to edit

Before moving on to the editor itself, let's look at the Scalable Vector Graphics, or SVG, we're going to edit (see Listing 1).

Listing 1. The basic SVG image file
<?xml version="1.0" encoding="UTF-8"?>
<svg:svg xmlns:svg="http://www.w3.org/2000/svg">

  <svg:g><svg:text stroke="red" stroke-width="1" x="350" y="85" 
           font-family="Verdana" font-size="36" font-style="italic" 
           fill="blue">Bombast, Inc.amp;lt;/svg:text></svg:g>
 
  <svg:g><svg:rect x="25" y="25" width="300" height="100" stroke="black" 
           rx="0" ry="0" stroke-width="3" fill="green"/></svg:g>

  <svg:g><svg:circle cx="115" cy="100" r="75" fill="red" stroke="blue" 
           stroke-width="10"/></svg:g>

</svg:svg>

Create a new file with this data and save it as logo.xml. Notice that it includes three different shapes, each with attributes that control its appearance. Each is part of a "group" element. Displayed by the browser, it looks like Figure 2.

Figure 2. The SVG image
The SVG image

Adding the SVG image to the page

Unfortunately, you can't just display the image on the page directly. The reason for this is twofold. First of all, both XForms and SVG are processed by individual extensions to the browser, so it is difficult to get them to talk to each other in order to, say, have the SVG renderer render data that exists only in the XForms extension. Similarly, JavaScript recognizes only the originally loaded instance, so you can't have it interact with edited data in order to display it. Secondly, XForms is designed to work with text. The ability to have it output entire elements (as opposed to their text content) is extremely limited, so you can't have it send the instance data to the page for display when it changes.

So to take care of it, you'll need to add an iframe you can update with a button (Listing 2).

Listing 2. The general page
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:my="http://example.com/my"
      xmlns:ev="http://www.w3.org/2001/xml-events"
      xmlns:XForms="http://www.w3.org/2002/XForms"
      xmlns:svg="http://www.w3.org/2000/svg">
  <head>
    <title>XForms Logo Generator</title>

  </head>
  <body>

    <h1 align="center">Logo Generator</h1>
    
    <form style="display: inline">
       <input type="button" value="Refresh View" 
                              onclick="logoframe.document.location.reload()"  /></form>

    <div style="width:100%;text-align: center; margin-top: 20px">
      <iframe id="logoframe" src="http://localhost/logo.xml" 
      width="640" height="200"></iframe>
    </div>

  </body>
</html>

The button enables the user to refresh the iframe after saving. Save this file as logogenerator.xhtml.


Editing the existing logo

In order to edit the XML, you first need to create an instance that includes the data (Listing 3).

Listing 3. Adding the data instance
<?xml version="1.0"?>
<html ...>
  <head>
    <title>XForms Logo Generator</title>

<xforms:model> <xforms:instance src="http://localhost/logo.xml" /></xforms:model>

  </head>
  <body>

    <h1 align="center">Logo Generator</h1>
...

Here you are creating the instance by referencing an external file, which just happens to include the SVG you created earlier. Now you need to create the controls that will enable you to edit the data (Listing 4).

Listing 4. Adding the editing controls

Click to see code listing

Listing 4. Adding the editing controls

...
  </head>
  <body>

    <h1 align="center">Logo Generator</h1>

    <xforms:repeat id="logorect" nodeset="/svg:svg/svg:g/svg:rect">  <xforms:label>Rectangle</xforms:label> <table> <tr> <td><xforms:input ref="@x">  <xforms:label>X:  </xforms:label></xforms:input></td>         <td><xforms:input ref="@y">                 <xforms:label>Y:  </xforms:label></xforms:input></td>       </tr>       <tr>         <td><xforms:input ref="@height">                <xforms:label>Height:  </xforms:label></xforms:input></td>        <td><xforms:input ref="@width">                <xforms:label>Width:  </xforms:label></xforms:input></td>     </tr>    <tr>      <td><xforms:input ref="@rx">             <xforms:label>RX:  </xforms:label></xforms:input></td>   <td><xforms:input ref="@ry">       <xforms:label>RY:  </xforms:label></xforms:input></td></tr><tr> <td><xforms:input ref="@stroke">    <xforms:label>Stroke:  </xforms:label></xforms:input></td> <td><xforms:input ref="@stroke-width">      <xforms:label>Stroke width: </xforms:label></xforms:input></td>      <td><xforms:input ref="@fill">          <xforms:label>Fill:</xforms:label></xforms:input></td>  </tr>  </table></xforms:repeat>

    <hr/>

    <xforms:repeat id="logocircle" nodeset="/svg:svg/svg:g/svg:circle">
...
    </xforms:repeat>
    
    <form style="display: inline">
       <input type="button" value="Refresh View" 
                              onclick="logoframe.document.location.reload()"  /></form>
...

There seems to be a lot of code here, but it boils down to this: Each type of shape has its own set of properties (and therefore attributes), so you create a section for each of your three major types: circle, rectangle, and text. For each one, include controls to edit the relevant attributes. For the text, you also include a control for editing the first child of the element, which is the actual text to display. You can format your XForms controls using CSS, but in this case, to keep things simple, table elements have been used. The result should look something like Figure 3.

Figure 3. Editing controls
editing controls

You can, of course, provide facilities for editing whatever shape you want. For example, you could include the ability to add and edit polylines, or other SVG shapes. The process is the same, so let's keep the article a little simpler by sticking to these three major shapes.

If you make changes to the data in the controls, the XForms engine will make changes to the instance, but if you click the Refresh View button, you won't see any changes. This is because the iframe is referencing the file, not the "in memory" version of the data. In order to see the changes, you are going to have to save the new data to the logo.xml file.


Save changes

The mechanics of saving the file are going to depend on where you have the file in the first place. If the file is local (or if your Web server supports it), you can use the PUT method, but this article assumes that the file is remote and that you will need a script to do the actual saving. The form of the actual script doesn't matter, as long as it outputs the XML back to the instance. I've included an example PHP script in the source code for this article. To call that script, you need to create a submission (Listing 5).

Listing 5. The submission

Click to see code listing

Listing 5. The submission

...
<xforms:model>
  <xforms:instance src="http://localhost/logo.xml" />

  <xforms:submission id="submitlogotosave" action="http://localhost/logoXForms.php"      replace="instance" method="post"/>

</xforms:model>

  </head>
  <body>
...
          <td><xforms:input ref="@fill">
                  <xforms:label>Fill: </xforms:label></xforms:input></td>
        </tr>
      </table>
    </xforms:repeat>

    <hr />

    <xforms:submit submission="submitlogotosave">    <xforms:label>Save Logo</xforms:label> </xforms:submit>       
    
    <form style="display: inline">
       <input type="button" value="Refresh View" 
                              onclick="logoframe.document.location.reload()"  /></form>
...

You create the submission element; in this case, a submission that sends the data to the PHP file, in the model. Rather than replacing the entire page, you only want the processor to replace the instance. This way, when the user submits the form, the page remains in place. This enables the user to save frequently in order to see small incremental changes without the process becoming overly cumbersome. To enable the user to actually save the data, you also added a XForms submit button that calls that submission.

To see this in action, refresh the page and make changes to the data in the controls. Click the Save Logo button and then click the Refresh View button to see the changes. The result is a page that should look something like Figure 4.

Figure 4. Saving the data
saving the data

Using this technique, you can make any changes you want to existing elements. Now you need to know how to add new elements to the image.


Inserting new elements

Up to now, things have been fairly straightforward. You have essentially been doing what XForms was designed to do, and doing it fairly simply. Now you are going to see the differences between how XForms was designed and the reality of what it can do. For example, you want to create a button that adds a new element to the document. In order to do that, you need to construct something like what is shown in Listing 6.

Listing 6. Adding the trigger

Click to see code listing

Listing 6. Adding the trigger

...
    </xforms:repeat>

    <hr />

    <xforms:trigger>     <xforms:label>Add New Element</xforms:label>   <xforms:action ev:event="DOMActivate">    <xforms:insert nodeset="/svg:svg/svg:g" at="1" position="before"/>   </xforms:action>    </xforms:trigger>

    <xforms:submit submission="submitlogotosave">
      <xforms:label>Save Logo</xforms:label>
    </xforms:submit>
...

What you're telling the editor to do is add a copy of the last g element at the top of the file, before element number one. That means if you reload the form and click the Add New Element button, you'll see a new Circle element, as shown in Figure 5.

Figure 5. Adding a new element
adding a new element

Obviously, this is not what you want; you want to be able to add an empty element. To make that happen, you can add a new element to the end of the logo.xml file (Listing 7).

Listing 7. Adding a new blank element
...
<svg:g><svg:circle cx="115" cy="100" r="75" fill="red" stroke="blue" 
            stroke-width="10"/></svg:g>

<svg:g>NEW ELEMENT -- CHOOSE SHAPE</svg:g>

</svg:svg>

The SVG g element doesn't define text as its body, so this text won't affect the actual image. It will, however, affect whether this is a "valid" SVG XML file. The Mozilla SVG implementation doesn't care, but if yours does, you'll want to adapt this technique so that you don't cause yourself problems. The actual text is completely arbitrary. You can then adapt to the editor to detect these elements (Listing 8).

Listing 8. Detecting text in the SVG elements

Click to see code listing

Listing 8. Detecting text in the SVG elements

...
          <td><xforms:input ref="@fill">
                  <xforms:label>Fill: </xforms:label></xforms:input></td>
        </tr>
      </table>
    </xforms:repeat>

    <hr/>

    <xforms:repeat id="logonew" nodeset="/svg:svg/svg:g[text()]">   <xforms:label>NEW ELEMENT -- CHOOSE SHAPE</xforms:label>  </xforms:repeat>

    <hr />

    <xforms:trigger>
      <xforms:label>Add New Element</xforms:label>
...

Here you have created a new control that displays a message only when it finds a g element that contains as its content a text node. You can see it displayed in Figure 6.

Figure 6. Displaying a message regarding the text-containing elements
Displaying a message regarding the text-containing elements

Now if you click the Add New Element button, you will see an additional message. This is because it is this new element you are cloning and adding to the document, as you can see in Figure 7.

Figure 7. Adding a new element
Adding a new element

If you save the file and look at it, you can see the new element in logo.xml, at the top of the file.


Copying elements from one instance to another

All right, you've got the ability to edit existing elements, and the ability to add new elements, but those new elements are pretty useless in terms of and into the actual image. In order to make them useful, you need the ability to populate them with one of your basic shapes.

To start with, you will create an instance that includes templates for the elements you wish to add (Listing 9).

Listing 9. Templates

Click to see code listing

Listing 9. Templates

...
<xforms:model>

  <xforms:instance id="content" src="http://localhost/logo.xml" />

  <xforms:instance id="templates">   <my:templates>      <my:shape my:label="Rectangle">       <svg:rect  x="320" y="20" width="20" height="20" stroke="red"  stroke-width="1"                 fill="blue" rx="0" ry="0" />   </my:shape>   <my:shape my:label="Text">      <svg:text stroke="red" stroke-width="1" x="320" y="50" font-family="Verdana"          font-size="24" font-style="italic"          fill="blue">Enter Text Here</svg:text> </my:shape>  <my:shape my:label="Circle">     <svg:circle cx="350" cy="50" r="20" fill="red" stroke="blue" stroke-width="1" /> </my:shape></my:templates> </xforms:instance>

  <xforms:submission id="submitlogotosave" action="http://localhost/logoXForms.php"
            instance="content" replace="instance"       method="post"/>

</xforms:model>

  </head>
  <body>

    <h1 align="center">Logo Generator</h1>


    <xforms:repeat id="logorect" nodeset="instance('content')/svg:g/svg:rect">
      <xforms:label>Rectangle</xforms:label>
      <table>
...

First notice that the new instance includes data in more than one namespace. Each template has been added in a generic my:shape element so that you can list the various available shapes. Second, notice that now that you have more than one instance, a reference has been added to the appropriate instance to each of your XPath statements. The instance() function replaces the root element in the XPath expression. (Note that technically you didn't have to specify the content instance, as, being the first instance in the model, it is the default. Consistency, however, says you should.)

Next you can add the control that enables you to choose from among these templates (Listing 10).

Listing 10. Copying existing elements

Click to see code listing

Listing 10. Copying existing elements

...
    <xforms:repeat id="logonew" nodeset="instance('content')/svg:g[text()]">
      <xforms:label>NEW ELEMENT -- CHOOSE SHAPE</xforms:label>
    </xforms:repeat>

    <hr />

    <xforms:select ref="instance('content')/svg:g[1]">     <xforms:label>Add new element(s):</xforms:label>   <xforms:itemset nodeset="instance('templates')/my:shape">     <xforms:label ref="@my:label"/>      <xforms:copy ref="./*"/>     </xforms:itemset>   </xforms:select>

    <xforms:trigger>
      <xforms:label>Add New Element</xforms:label>
...

This one is a little complicated, so let's take it from the top. First, you are dealing with a select element, used to add data to a specific node based on the users choices. The note to which this data will be added is the first g element in the content instance. Remember, this is the element you added with Add New Element. The actual data that's added to this node depends on the selected itemset items. In this case, those items come from the templates instance, and consist of each of the shape elements. For each one, the label is the label attribute of the shape element.

But where you would normally follow the label element with a value element, you are instead using a copy element, which references the child of the shape element, or the circle, rect, or text template. From the user standpoint, the result looks like Figure 8.

Figure 8. The select control
The select control

When the user clicks on one of these labels to select the element, the XForms engine copies that element to the target g element. You can see the results in Figure 11, where you now have an extra Rectangle. If, on the other hand, the user deselects that item, perhaps by clicking a different item, the copy is removed and, in this case, replaced by the new copy. For example, if you now click the Circle item and save the logo, refreshing the view shows the results in Figure 9.

Figure 9. Adding a circle
Adding a circle

There is still one more problem. If the user simply loads the page and clicks a new element type before any new element, you can get some fairly terrible results, because you are simply copying the template into the first g element -- which in this case is actual data, and not a placeholder. To solve that problem, you can add an empty g element to the top of the file (Listing 11).

Listing 11. Adding an empty placeholder
<?xml version="1.0" encoding="UTF-8"?>
<svg:svg xmlns:svg="http://www.w3.org/2000/svg">

<svg:g></svg:g>

<svg:g><svg:text stroke="red" stroke-width="1" x="350" y="85" 
           font-family="Verdana" font-size="36" font-style="italic"
...

To afford further confusion, you can also change the editors so that it only displays an empty element if one has been added by the user (Listing 12).

Listing 12. Limiting the display of new elements
...
    <xforms:repeat id="logonew" nodeset="instance('content')/svg:g[text()][2]">
      <xforms:label>NEW ELEMENT -- CHOOSE SHAPE</xforms:label>
    </xforms:repeat>
...

This way, the form only displays a message for a second text-containing element.


Deleting existing elements

You are almost done, but you need to add one more piece of very important functionality. To do that, you can use the delete action (Listing 13).

Listing 13. Adding delete buttons

Click to see code listing

Listing 13. Adding delete buttons

...
    <xforms:repeat id="logorect" nodeset="instance('content')/svg:g/svg:rect">
      <xforms:label>Rectangle</xforms:label>
      <table>
        <tr>
          <td rowspan="4"> <xforms:trigger> <xforms:label>Delete</xforms:label> <xforms:action ev:event="DOMActivate">   <xforms:delete nodeset="." at="1" />  </xforms:action>  </xforms:trigger> </td>
          <td><xforms:input ref="@x">
                  <xforms:label>X: </xforms:label></xforms:input></td>
...

It's possible to create a delete button that then leaves the "current" element, but to keep things simple, you are adding a delete button for each individual element. Clicking that button removes the current element from the instance. The result looks like Figure 10.

Figure 10. Delete buttons
delete buttons

Now you can add, edit, and delete elements at will.


Starting from scratch -- the "empty" logo file

You started this project with an existing logo.xml file, so the last step is to make sure that you can replicate your success with a file that does not contain a pre-existing image. There are, however, very specific requirements for pre-existing elements, so you can't actually start with an empty file. Instead, you need the following (Listing 14).

Listing 14. The "empty" file
<?xml version="1.0" encoding="UTF-8"?>
<svg:svg xmlns:svg="http://www.w3.org/2000/svg">

  <svg:g></svg:g>

  <svg:g>NEW ELEMENT -- CHOOSE SHAPE</svg:g>

</svg:svg>

Essentially, you need two pre-existing elements. The first is the placeholder into which you can add new elements. The second is the element to clone for when the user clicks the Add New Element button. Replace the contents of your logo to an XML file with the text of Listing 14 and save the file. Refresh the editor so you will have the ability to create an image from scratch, such as the one seen in Figure 11.

Figure 11. Creating a new image
Creating a new image

It works!


Summary

In this article, you looked at what it takes to create an XForms form capable of creating and editing an SVG image. What you learned is that you can use XForms' ability to insert new elements and copy information into them in order to add new content. You can also use XForms' traditional element- and attribute-editing capabilities to alter the properties of SVG objects. From there, you can save the data to a file and view changes to the file.

In a production environment, you will want to make several changes. For one thing, you'll want each individual user to have his or her own logo.XML file, probably based on a unique username. You might want to expand the users' choice of shapes, or the properties available for editing. You might also provide a way to download a non-SVG version of the logo, such as in a PDF or PNG file.

But the basic technique is right here.


Download

DescriptionNameSize
Logo generator source codelogogenerator_source.zip3KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=196537
ArticleTitle=Creating an XForms-based logo generator
publish-date=02202007