310 likes | 422 Views
YAPC::EU 2003. Building an SVG GUI with Perl. Ronan Oger RO IT Systems GmbH Ronan@roasp.com Ronan.oger@roitsystems.com YAPC::Europe 2003. Freely available for download Cross-platform Freely available for download Cross-platform Tested on Sun, Windows, Mac OSX, Cygwin, Linux, FreeBSD
E N D
YAPC::EU 2003 Building an SVG GUIwith Perl Ronan Oger RO IT Systems GmbH Ronan@roasp.com Ronan.oger@roitsystems.com YAPC::Europe 2003
Freely available for download Cross-platform Freely available for download Cross-platform Tested on Sun, Windows, Mac OSX, Cygwin, Linux, FreeBSD Pure-Perl: designed to avoid Perl
Freely available for download under Perl Artistic License Installable many ways: PPM FreeBSD Make CPAN installation through Perl Pure-Perl: Root access not required to implement Cross-platform SVG.pm
CPAN: Perl –MCPAN –e `install SVG‘ Perl Package Manager (win32): PPM PPM>Install SVG Make: Gunzip svg-xx.xxx.tar.gz Tar –xvf svg-xx-xxx.tar Make Make test Make install Installing SVG.pm
Free – Perl Artistic License You get what you pay for... You can ask for special features... SVG.pm features
Hello SVG World! Draw a line Navigating the DOM Tessalate YAPH SVG.pm Usage Examples
Hello SVG World! #!/usr/bin/perl -w use strict; use SVG; print "Content-Type: image/svg+xml\n\n"; my $svg=new SVG(); $svg->rect(id=>'rect1',x=>'20px',y=>'55px', width=>10,height=>10,fill=>'yellow'); my $text = $svg->text(x=>20, y=>55, fill=>'red', stroke=>'black'); $text->cdata("Hello SVG world!"); #grab the fill element my $fill = $svg->getElementByID('rect1') ->getAttribute('fill'); #modify the fill attribute of the text element $text->setAttribute('fill',$fill); print $svg->render(); #xmlify http://www.roitsystems.com/conferences/yapc_eu/code/hello_world.txt
Draw a line #!/usr/bin/perl -w use strict; use SVG; print "Content-Type: image/svg+xml\n\n"; my $svg=new SVG(width=>60, height=>45); my $group1=$svg->group(id=>"outer_group"); my $group2=$group1->group(id=>"inner_group"); $group2->line(x1=>20, y1=>30, x2=>50, y2=>35, stroke=>"blue"); print $svg->render(); http://www.roitsystems.com/conferences/yapc_eu2003/code/01b-GroupedLine.txt
Navigate the DOM ...part 1 #!/usr/bin/perl –w use strict; use SVG; print "Content-Type: image/svg+xml\n\n"; my $svg=new SVG(width=>60, height=>60); my $group1=$svg->group(id=>"outer_group"); $group1->rect(x=>10, y=>10, width=>40, height=>40, fill=>"yellow"); my $group2=$group1->group(id=>"inner_group", stroke=>"blue"); $group2->line(x1=>10, y1=>30, x2=>50, y2=>40); $group2->line(x1=>30, y1=>10, x2=>40, y2=>50); $group2->line(x1=>10, y1=>40, x2=>40, y2=>10); my $anchor = $group1->anchor(-href=>'http://example.net'); my $circle = $anchor->circle(cx=>30, cy=>30, r=>6, fill=>"red"); $circle->set(begin=>"mouseover", end=>"mouseout", attributeName=>'fill', to=>'cyan'); print $svg->render(); #or xmlify or serialize http://www.roitsystems.com/conferences/yapc_eu2003/code/01i-FirstNextIterator.txt
Navigate the DOM ...part 2 iterate($svg); sub iterate { my ($element,$depth)=@_; $depth=0 unless defined $depth; my $child=$element->getFirstChild(); return unless $child; do { print "\t"x$depth, "Element $child is a ", $child->getElementName(), "\n"; iterate($child,$depth+1) if $child->hasChildren; } while ($child = $child->getNextSibling); }
Tessalate ...part 1 #!/usr/bin/perl -w use strict; use SVG; my $svg = SVG->new(width=>"100%", height=>"100%"); my $g = $svg->group(style=>{"fill-rule"=>"evenodd","stroke-linejoin"=>"round", "stroke-linecap"=>"round"}); my $d1 = $g->defs(); my $path = $svg->get_path(-type=>"path", x=>[0,90,60], y=>[0,60,90], -closed=>1); my $d1g1p = $d1->group(id=>"Tess0p")->path(%$path); my $d1g0 = $d1->group( id=>"Tess0", fill=>"rgb(255,255,0)", stroke=>"none")->use(-href=>"#Tess0p"); http://www.roitsystems.com/conferences/yapc_eu2003/code/tessalate.txt
Tessalate ...part 2 my $d1g1 = $d1->group(id=>"Tess1", fill=>"none", stroke=>"rgb(0,0,0)", "stroke-width"=>"2.413")->use(-href=>"#Tess0p"); $path = $svg->get_path(-type=>"path", x=>[15,75,50], y=>[15,50,75], -closed=>1); my $d1g2p = $d1->group( id=>"Tess2p")->path(%$path); my $d1g2 = $d1->group(id=>"Tess2", fill=>"rgb(255,170,255)", stroke=>"none")->use(-href=>"#Tess2p"); my $d1g3 = $d1->group( id=>"Tess3", fill=>"none", stroke=>"rgb(0,0,0)", "stroke-width"=>"2.413")->use(-href=>"#Tess2p"); my $d2p2 = $g->defs()->pattern(id=>"TessPattern", patternUnits=>"userSpaceOnUse", x=>"0", y=>"0", width=>"100", height=>"100", viewBox=>"0 0 100 100", overflow=>"visible"); $d2p2->group()->use(-href=>"#Tess0"); $d2p2->group()->use(-href=>"#Tess1"); $d2p2->group()->use(-href=>"#Tess2"); $d2p2->group()->use(-href=>"#Tess3");
Tessalate ...part 3 $svg->comment('Now let us define the polygon with the fill inside it refered to by url reference'); $svg->polygon(points=>"163.816,337.5 ".(140+rand(20)).",".(400+rand(60))." 234.868,344.079 334.868,428.289 291.447,299.342 480.921,284.868 326.974,".(160+rand(60))." 344.079,30.9211 232.237,167.763 123.026,29.6053 150.658,191.447 37.5,94.0789 ".(100+rand(10)).','.(200+rand(40))." 7.23684,288.816 84.8684,287.5 33.5526,333.553 111.184,320.395 82.2368,448.026",fill=>"url(#TessPattern)", stroke=>"black"); $svg->text(x=>100,y=>20)->cdata("Using A tessalated pattern to create a fill"); $svg->anchor(-href=>'http://roasp.com/tutorial/source/tessalate.txt')->text(x=>50,y=>400, fill=>'red' )->cdata("View Script Source"); print "Content-type: image/svg+xml\n\n"; print $svg->xmlify;
Minimize client-side functional requirements Keep business logic on the server Facilitate functional extension Require planing and vision Thin-client applications
Now: HTML-Style form process model Render->modify->submit Familar with users. (Possibly) Later: XFORMS support in SVG Embedded form content within the XML vocabulary. Problem: Not in 1.2. Not finalized. No processing model. Too complex. Never worked with it... Forms Processing in SVG
HTML: declarative form processing Must submit all values and refresh page SVG 1.0-1-2: No support for declarative information processing Reliant on scripting HTML-Style Forms
Keep the widget a ‘black box‘ Wiget changes modify a text field Maintain state at server Use commit event to update state HTML-Style Architecture Concept
Rotary control Sliding control Pull-down menu Examples http://www.roitsystems.com/conferences/yapc_eu2003/svg/knob.svg http://www.roitsystems.com/conferences/yapc_eu2003/svg/slider.svg http://www.roitsystems.com/conferences/yapc_eu2003/svg/pulldown.svg
Commit button Do_process_form getURL (postURL) Callback_method – rendering commands. Old: delete by id New: append to the workspace Message: server messages for the user How this works...
do_set_string: Assign the interface values do_process_form: Submit the form show_callback: handle the result from getURL script components
Provides a single method for assigning values to the form interface. Required due to a shortcoming in SMIL which does not support declarative animation on text elements. do_set_string() // set a string function do_set_string(Doc,id,string) { Doc.getElementById(id).getFirstChild.setData(string); }
Form handler for submit. Required due to a shortcoming in SMIL which does not support declarative animation on text elements. do_process_form() // submit the form for processing function do_process_form(IDArray,ValueArray) { var url = 'GISMax.cgi?edit=;act=processform;'; for (var i=0; i<IDArray.length; i++) { url = url + eval("'"+IDArray[i]+"'") + "=„ + getFormFieldValue(evt,eval("'"+ValueArray[i]+"'"))+';'; } getURL(url+';uid='+uid,show_callback); setCommand(evt,''); }
Form handler for submit. Required due to a shortcoming in SMIL which does not support declarative animation on text elements. Show_callback() ...start function show_callback(test) { //handle the raw return and parse the data grp=parseXML('<g>'+test.content+'</g>',document); if (grp.hasChildNodes) { vp = document.getElementById('canvas'); //handle <old> nodes engine = grp.getFirstChild(); xml = engine.getFirstChild(); //...continued next slide
Dispose of old elements by ID that the server declares as deletable Show_callback() ...old oldgroup = xml.getElementsByTagName('obj'); // I think this is now an array var length = oldgroup.getLength(); i=0; var oldId = "none"; while (i < length) { thisElement=oldgroup.item(i); oldId = thisElement.getAttribute("id"); myself = document.getElementById("g."+oldId); if (myself) { myself.parentNode.removeChild(myself); myself = null; } i++; do_set_string(document,'message',"Attached anchor "+oldId); } // ...continued
Message handler for feedback at the client side Show_callback() ...msg //message handler //grab the message we recieved message_body = xml.getElementsByTagName('message'); if (message_body) { if (messageElement=message_body.item(0)) { message = messageElement.getAttribute('value'); do_set_string(SVGDoc,'message',message); } }
Insert new SVG snippet into workspace Show_callback() ...news //handle new elements. Each element to be added is actually a group. news = xml.getElementsByTagName('new'); length = news.getLength(); if (length == 0) {return} var i=0; while (i<length) { xml = news.item(0); newgroup = xml.getElementsByTagName('g'); var groups = newgroup.getLength(); //catch error (2): missing <g> tags in <new> tag if (groups == 0) {return} var j = 0; while (j<length) { vp.appendChild(newgroup.item(j)); j++; } } } //for completeness. End of subroutine
Script function Do_process_form causes a query to be called by getURL and to be parsed by parsXML Result of pulldown.svg server query XML snippet result <engine id="engine"> <old id="oldgroups"> <OldID id="text_from_server" /> </old> <NewGroup id="newgroup"> <g id="g.text_from_server"> <rect width="100" y="20" fill="green" stroke="blue" x="10" height="60" /> <text y="24" fill="red" x="23"> Hey... you said: Dog</text> </g> </NewGroup> <messages id="messages"> <message value="Hey, you said something" /> </messages> </engine> http://localhost/cgi-bin/svgopen2003/gui_workshop/processor.pl?gui_form_x_value=Dog
Keep the client-side functionality simple Reduce implementation risk Minimize cross-platform incompatibilities No large clientside codebase Reduce user wait before getting started Obfuscates code base – some people like this Client-side machine has minimal business logic Applications can not be reverse-engineered by users Well suited for collaboration applications All data stored on centralized servers Reduces workstation failure risk Increases security No user access to data unless explicitly enabled Why this stuff is good
Must stay connected to the server – no standalone apps. Not always on line. Bandwidth. Mass-market sites not a good idea. Better suited for intranet systems. Distance to host affects user experience Server load – Requires powerful hardware. Server intensive Can be minimized by using caching Down sides
Lag: Round-tripping data takes time Not suitabable for very fast response systems Need to allow for communication failures Incomplete Browser support Requires SVG and JS Limited to Adobe 3+, Corel Batik. ASV3 is broken on Mozilla 1. Native SVG support in Mozilla still experimental JS/SVG has Issues on non-Windows platforms Down sides ...2
Sites http//www.roasp.com/ Serverside SVG portal – by RO IT Systems http://www.w3.org/TR/SVG/1.0/ SVG recommendation – Comprehensive SVG documentation with extensive examples http://www.svgopen.org SVG Open conference home – Repository of papers and presentations from past conferences dating back to SVG Open 2002 http://www.scale-a-vector.de Petra Kukofka‘s SVG design page – SVG cartoons and animations, discussions, SVG articles http://www.spark.org SPARK project: SVG GUI issues – working on the development of a common GUI framework for SVG applications http://www.pinkjuice.com/svg/ Prolific SVG page by Tobias Rief in Berlin http://www.svgfoundation.org SVG Foundation http://www.protocol7.com/svg/wiki/ The SVG wiki – excellent site for cross-reference http://www.svg-cafe.net An SVG discussion forum http://www.carto.net/ A rich cartography-specific SVG site Newsgroups svg-developers@yahoogroups.com Books SVG Unleashed Sams Publishing - 2002 Professional Perl – Wrox Press - 2001 Learning Perl – O‘Reilly Press – 1995-2000 Resources