SlideShare a Scribd company logo
SILVERSTRIPE CMS
              JAVASCRIPT REFACTORING
                         The jsparty is over!




Friday, 28 August 2009
SCOPE
              • JavaScript    library upgrade, no more forks

              • CMS JavaScript cleanup
                Rewrite in jQuery where feasible

              • CMS      PHP Controller simplification

              • More      solid UI components with jQuery UI and plugins

              • CSS Architecture     for easier customization

              • Not      a large-scale CMS redesign
Friday, 28 August 2009
LIBRARIES

    • Prototype           1.4rc3 to jQuery 1.3

    • Random             UI code and ancient libs to jQuery UI 1.7

    • Custom             behaviour.js to jQuery.concrete

    • External   libraries managed by Piston (piston.rubyforge.org/)
        instead of svn:externals


Friday, 28 August 2009
GOODBYE JSPARTY
         • Merged   external libraries
             to cms/thirdparty and sapphire/thirdparty




Friday, 28 August 2009
THE DARK AGES

                         function hover_over() {
                           Element.addClassName(this, 'over');
                         }
                         function hover_out() {
                           Element.removeClassName(this, 'over');
                         }

                         hover_behaviour = {
                           onmouseover : hover_over,
                           onmouseout : hover_out
                         }
                         jsparty/hover.js




Friday, 28 August 2009
THE DARK AGES
                 class LeftAndMain {
            // ...
                public function returnItemToUser($p) {
                   // ...
                   $response = <<<JS
                     var tree = $('sitetree');
                     var newNode = tree.createTreeNode("$id", "$treeTitle", "{$p->class}{$hasChildren}
            {$singleInstanceCSSClass}");
                     node = tree.getTreeNodeByIdx($parentID);
                     if(!node) {
                       node = tree.getTreeNodeByIdx(0);
                     }
                     node.open();
                     node.appendTreeNode(newNode);
                     newNode.selectTreeNode();
            JS;
                   FormResponse::add($response);
                   FormResponse::add($this->hideSingleInstanceOnlyFromCreateFieldJS($p));

                     return FormResponse::respond();
                }
            }

          cms/code/LeftAndMain.php (excerpt)




Friday, 28 August 2009
BEST PRACTICES


                         • Don’t   claim global properties

                         • Assume    element collections

                         • Encapsulate: jQuery.concrete, jQuery   plugin,
                          jQueryUI widget (in this order)



Friday, 28 August 2009
ENCAPSULATE: EXAMPLE
                                    Simple Highlight jQuery Plugin
                         // create closure
                         (function($) {
                           // plugin definition
                           $.fn.hilight = function(options) {
                              // build main options before element iteration
                              var opts = $.extend({}, $.fn.hilight.defaults, options);
                              // iterate and reformat each matched element
                              return this.each(function() {
                                $this = $(this);
                                // build element specific options
                                var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
                                // update element styles
                                $this.css({backgroundColor: o.background,color: o.foreground});
                              });
                           };
                           // plugin defaults
                           $.fn.hilight.defaults = {foreground: "red",background: "yellow"};
                         // end of closure
                         })(jQuery);




Friday, 28 August 2009
BEST PRACTICES


                         • Useplain HTML, jQuery.data() and
                          jQuery.metadata to encode initial state
                          and (some) configuration

                         • Better
                                than building “object cathedrals” in
                          most cases



Friday, 28 August 2009
STATE: EXAMPLE
                              Simple Form Changetracking

    State in CSS                 $('form :input').bind('change', function(e) {
                                   $(this.form).addClass('isChanged');
                                 });
    properties                   $('form').bind('submit', function(e) {
                                   if($(this).hasClass('isChanged')) return false;
                                 });




                                 $('form :input').bind('change', function(e) {

    State in DOM                   $(this.form).data('isChanged', true);
                                 });
                                 $('form').bind('submit', function(e) {
    (through jQuery.data())        if($(this).data('isChanged')) return false;
                                 });




Friday, 28 August 2009
BEST PRACTICES
    • Ajax               responses should default to HTML
        Makes it easier to write unobtrusive markup and use SilverStripe templating

    • Use           HTTP metadata to transport state and additional data
        Example: 404 HTTP status code to return “Not Found”
        Example: Custom “X-Status” header for more detailed UI status

    • Return               JSON if HTML is not feasible
        Example: Update several tree nodes after CMS batch actions
        Alternative: Inspect the returned DOM for more structured data like id attributes

    • For AJAX               and parameter transmission, <form> is your friend

Friday, 28 August 2009
AJAX: EXAMPLE
                   Simple Serverside Autocomplete for Page Titles
                          <form action"#">
                            <div class="autocomplete {url:'MyController/autocomplete'}">
                              <input type="text" name="title" />
                              <div class="results" style="display: none;">
                            </div>
                            <input type="submit" value="action_autocomplete" />
                          </form>
                          MyController.ss


                                                                                       Using jQuery.metadata,
                                                                            but could be a plain SilverStripe Form as well


                                            <ul>
                                            <% control Results %>
                                              <li id="Result-$ID">$Title</li>
                                            <% end_control %>
                                            </ul>
                                            AutoComplete.ss




Friday, 28 August 2009
AJAX: EXAMPLE

                         class MyController {
                           function autocomplete($request) {
                             $SQL_title = Convert::raw2sql($request->getVar('title'));
                             $results = DataObject::get("Page", "Title LIKE '%$SQL_title%'");
                             if(!$results) return new HTTPResponse("Not found", 404);

                                 // Use HTTPResponse to pass custom status messages
                                 $this->response->setStatusCode(200, "Found " . $results->Count() . " elements");

                                 // render all results with a custom template
                                 $vd = new ViewableData();
                                 return $vd->customise(array(
                                   "Results" => $results
                                 ))->renderWith('AutoComplete');
                             }
                         }
                         MyController.php




Friday, 28 August 2009
AJAX: EXAMPLE

                         $('.autocomplete input').live('change', function() {
                           var resultsEl = $(this).siblings('.results');
                           resultsEl.load(
                              // get form action, using the jQuery.metadata plugin
                              $(this).parent().metadata().url,
                              // submit all form values
                              $(this.form).serialize(),
                              // callback after data is loaded
                              function(data, status) {
                                resultsEl.show();
                                // optional: get all record IDs from the new HTML
                                var ids = jQuery('.results').find('li').map(function() {
                                  return $(this).attr('id').replace(/Record-/,'');
                                });
                              }
                           );
                         });
                         MyController.js




Friday, 28 August 2009
BEST PRACTICES

    • Use           events and observation for component communication

    • Use           composition for synchronous, two-way communicatoin

    • Use           callbacks to allow for customization

    • Don’t              overuse prototypical inheritance and pseudo-classes

    • Only   expose public APIs where necessary
        (through jQuery.concrete)

Friday, 28 August 2009
RESOURCES

                              Documentation (unreleased)
                          http://doc.silverstripe.com/doku.php?id=2.4:javascript



                         Source (unreleased, pre 2.4 alpha)
                          http://github.com/chillu/sapphire/tree/jsrewrite

                             http://github.com/chillu/cms/tree/jsrewrite



Friday, 28 August 2009
Ad

Recommended

ODP
Getting to Grips with SilverStripe Testing
Mark Rickerby
 
PDF
Unit Testing in SilverStripe
Ingo Schommer
 
PPTX
Good karma: UX Patterns and Unit Testing in Angular with Karma
ExoLeaders.com
 
PPT
Testing Javascript with Jasmine
Tim Tyrrell
 
PPTX
Protractor Training in Pune by QuickITDotnet
QuickITDotNet Training and Services
 
PDF
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
GeeksLab Odessa
 
PDF
Introduction to Protractor
Jie-Wei Wu
 
PDF
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
GeeksLab Odessa
 
PDF
JavaScript Unit Testing with Jasmine
Raimonds Simanovskis
 
PPTX
Javascript Testing with Jasmine 101
Roy Yu
 
PDF
Unit Testing JavaScript Applications
Ynon Perek
 
PDF
Jasmine - why JS tests don't smell fishy
Igor Napierala
 
PDF
How to write easy-to-test JavaScript
Ynon Perek
 
PDF
Test-Driven Development of AngularJS Applications
FITC
 
PDF
Getting Testy With Perl6
Workhorse Computing
 
PDF
Testing JavaScript Applications
The Rolling Scopes
 
PDF
Testing your javascript code with jasmine
Rubyc Slides
 
PDF
Rails is not just Ruby
Marco Otte-Witte
 
PDF
Excellent
Marco Otte-Witte
 
PDF
RSpec 3.0: Under the Covers
Brian Gesiak
 
PDF
Adventures In JavaScript Testing
Thomas Fuchs
 
PDF
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia
 
PDF
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 
PDF
UA testing with Selenium and PHPUnit - PFCongres 2013
Michelangelo van Dam
 
PDF
Unit testing with mocha
Revath S Kumar
 
PDF
PhpSpec 2.0 ilustrated by examples
Marcello Duarte
 
PDF
Class-based views with Django
Simon Willison
 
PDF
jQuery for beginners
Siva Arunachalam
 
PDF
jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)
Doris Chen
 

More Related Content

What's hot (20)

PDF
JavaScript Unit Testing with Jasmine
Raimonds Simanovskis
 
PPTX
Javascript Testing with Jasmine 101
Roy Yu
 
PDF
Unit Testing JavaScript Applications
Ynon Perek
 
PDF
Jasmine - why JS tests don't smell fishy
Igor Napierala
 
PDF
How to write easy-to-test JavaScript
Ynon Perek
 
PDF
Test-Driven Development of AngularJS Applications
FITC
 
PDF
Getting Testy With Perl6
Workhorse Computing
 
PDF
Testing JavaScript Applications
The Rolling Scopes
 
PDF
Testing your javascript code with jasmine
Rubyc Slides
 
PDF
Rails is not just Ruby
Marco Otte-Witte
 
PDF
Excellent
Marco Otte-Witte
 
PDF
RSpec 3.0: Under the Covers
Brian Gesiak
 
PDF
Adventures In JavaScript Testing
Thomas Fuchs
 
PDF
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia
 
PDF
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 
PDF
UA testing with Selenium and PHPUnit - PFCongres 2013
Michelangelo van Dam
 
PDF
Unit testing with mocha
Revath S Kumar
 
PDF
PhpSpec 2.0 ilustrated by examples
Marcello Duarte
 
PDF
Class-based views with Django
Simon Willison
 
JavaScript Unit Testing with Jasmine
Raimonds Simanovskis
 
Javascript Testing with Jasmine 101
Roy Yu
 
Unit Testing JavaScript Applications
Ynon Perek
 
Jasmine - why JS tests don't smell fishy
Igor Napierala
 
How to write easy-to-test JavaScript
Ynon Perek
 
Test-Driven Development of AngularJS Applications
FITC
 
Getting Testy With Perl6
Workhorse Computing
 
Testing JavaScript Applications
The Rolling Scopes
 
Testing your javascript code with jasmine
Rubyc Slides
 
Rails is not just Ruby
Marco Otte-Witte
 
Excellent
Marco Otte-Witte
 
RSpec 3.0: Under the Covers
Brian Gesiak
 
Adventures In JavaScript Testing
Thomas Fuchs
 
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia
 
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 
UA testing with Selenium and PHPUnit - PFCongres 2013
Michelangelo van Dam
 
Unit testing with mocha
Revath S Kumar
 
PhpSpec 2.0 ilustrated by examples
Marcello Duarte
 
Class-based views with Django
Simon Willison
 

Similar to SilverStripe CMS JavaScript Refactoring (20)

PDF
jQuery for beginners
Siva Arunachalam
 
PDF
jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)
Doris Chen
 
PDF
Idiots guide to jquery
Mark Casias
 
PPT
J query b_dotnet_ug_meet_12_may_2012
ghnash
 
KEY
User Interface Development with jQuery
colinbdclark
 
PDF
Write Less Do More
Remy Sharp
 
PDF
Web2.0 with jQuery in English
Lau Bech Lauritzen
 
PDF
fuser interface-development-using-jquery
Kostas Mavridis
 
PDF
What's this jQuery? Where it came from, and how it will drive innovation
Marakana Inc.
 
PDF
jQuery Loves Developers - Oredev 2009
Remy Sharp
 
PPTX
JQuery
Rahul Jain
 
PDF
Cleaner, Leaner, Meaner: Refactoring your jQuery
Rebecca Murphey
 
KEY
An in-depth look at jQuery
Paul Bakaus
 
PDF
jQuery Introduction
Arwid Bancewicz
 
PPTX
How I Learned to Stop Worrying and Love jQuery (Jan 2013)
David Giard
 
KEY
Jquery Fundamentals
Rebecca Murphey
 
PDF
Remy Sharp The DOM scripting toolkit jQuery
deimos
 
PDF
J query fundamentals
Attaporn Ninsuwan
 
PDF
Download full ebook of Extending jQuery Keith Wood download pdf instant downl...
busicluckesz
 
PPTX
jQuery
Jay Poojara
 
jQuery for beginners
Siva Arunachalam
 
jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)
Doris Chen
 
Idiots guide to jquery
Mark Casias
 
J query b_dotnet_ug_meet_12_may_2012
ghnash
 
User Interface Development with jQuery
colinbdclark
 
Write Less Do More
Remy Sharp
 
Web2.0 with jQuery in English
Lau Bech Lauritzen
 
fuser interface-development-using-jquery
Kostas Mavridis
 
What's this jQuery? Where it came from, and how it will drive innovation
Marakana Inc.
 
jQuery Loves Developers - Oredev 2009
Remy Sharp
 
JQuery
Rahul Jain
 
Cleaner, Leaner, Meaner: Refactoring your jQuery
Rebecca Murphey
 
An in-depth look at jQuery
Paul Bakaus
 
jQuery Introduction
Arwid Bancewicz
 
How I Learned to Stop Worrying and Love jQuery (Jan 2013)
David Giard
 
Jquery Fundamentals
Rebecca Murphey
 
Remy Sharp The DOM scripting toolkit jQuery
deimos
 
J query fundamentals
Attaporn Ninsuwan
 
Download full ebook of Extending jQuery Keith Wood download pdf instant downl...
busicluckesz
 
jQuery
Jay Poojara
 
Ad

Recently uploaded (20)

PDF
Cyber Defense Matrix Workshop - RSA Conference
Priyanka Aash
 
PPTX
Curietech AI in action - Accelerate MuleSoft development
shyamraj55
 
PDF
Enhance GitHub Copilot using MCP - Enterprise version.pdf
Nilesh Gule
 
PDF
Using the SQLExecutor for Data Quality Management: aka One man's love for the...
Safe Software
 
PDF
AI Agents and FME: A How-to Guide on Generating Synthetic Metadata
Safe Software
 
PPTX
CapCut Pro Crack For PC Latest Version {Fully Unlocked} 2025
pcprocore
 
PPTX
You are not excused! How to avoid security blind spots on the way to production
Michele Leroux Bustamante
 
PDF
Quantum AI Discoveries: Fractal Patterns Consciousness and Cyclical Universes
Saikat Basu
 
PDF
"Database isolation: how we deal with hundreds of direct connections to the d...
Fwdays
 
PDF
AI vs Human Writing: Can You Tell the Difference?
Shashi Sathyanarayana, Ph.D
 
PDF
EIS-Webinar-Engineering-Retail-Infrastructure-06-16-2025.pdf
Earley Information Science
 
PDF
Agentic AI for Developers and Data Scientists Build an AI Agent in 10 Lines o...
All Things Open
 
PDF
The Growing Value and Application of FME & GenAI
Safe Software
 
PPTX
" How to survive with 1 billion vectors and not sell a kidney: our low-cost c...
Fwdays
 
PDF
GenAI Opportunities and Challenges - Where 370 Enterprises Are Focusing Now.pdf
Priyanka Aash
 
PDF
Securing AI - There Is No Try, Only Do!.pdf
Priyanka Aash
 
PDF
ReSTIR [DI]: Spatiotemporal reservoir resampling for real-time ray tracing ...
revolcs10
 
PDF
"Scaling in space and time with Temporal", Andriy Lupa.pdf
Fwdays
 
PDF
Smarter Aviation Data Management: Lessons from Swedavia Airports and Sweco
Safe Software
 
PDF
Mastering AI Workflows with FME by Mark Döring
Safe Software
 
Cyber Defense Matrix Workshop - RSA Conference
Priyanka Aash
 
Curietech AI in action - Accelerate MuleSoft development
shyamraj55
 
Enhance GitHub Copilot using MCP - Enterprise version.pdf
Nilesh Gule
 
Using the SQLExecutor for Data Quality Management: aka One man's love for the...
Safe Software
 
AI Agents and FME: A How-to Guide on Generating Synthetic Metadata
Safe Software
 
CapCut Pro Crack For PC Latest Version {Fully Unlocked} 2025
pcprocore
 
You are not excused! How to avoid security blind spots on the way to production
Michele Leroux Bustamante
 
Quantum AI Discoveries: Fractal Patterns Consciousness and Cyclical Universes
Saikat Basu
 
"Database isolation: how we deal with hundreds of direct connections to the d...
Fwdays
 
AI vs Human Writing: Can You Tell the Difference?
Shashi Sathyanarayana, Ph.D
 
EIS-Webinar-Engineering-Retail-Infrastructure-06-16-2025.pdf
Earley Information Science
 
Agentic AI for Developers and Data Scientists Build an AI Agent in 10 Lines o...
All Things Open
 
The Growing Value and Application of FME & GenAI
Safe Software
 
" How to survive with 1 billion vectors and not sell a kidney: our low-cost c...
Fwdays
 
GenAI Opportunities and Challenges - Where 370 Enterprises Are Focusing Now.pdf
Priyanka Aash
 
Securing AI - There Is No Try, Only Do!.pdf
Priyanka Aash
 
ReSTIR [DI]: Spatiotemporal reservoir resampling for real-time ray tracing ...
revolcs10
 
"Scaling in space and time with Temporal", Andriy Lupa.pdf
Fwdays
 
Smarter Aviation Data Management: Lessons from Swedavia Airports and Sweco
Safe Software
 
Mastering AI Workflows with FME by Mark Döring
Safe Software
 
Ad

SilverStripe CMS JavaScript Refactoring

  • 1. SILVERSTRIPE CMS JAVASCRIPT REFACTORING The jsparty is over! Friday, 28 August 2009
  • 2. SCOPE • JavaScript library upgrade, no more forks • CMS JavaScript cleanup Rewrite in jQuery where feasible • CMS PHP Controller simplification • More solid UI components with jQuery UI and plugins • CSS Architecture for easier customization • Not a large-scale CMS redesign Friday, 28 August 2009
  • 3. LIBRARIES • Prototype 1.4rc3 to jQuery 1.3 • Random UI code and ancient libs to jQuery UI 1.7 • Custom behaviour.js to jQuery.concrete • External libraries managed by Piston (piston.rubyforge.org/) instead of svn:externals Friday, 28 August 2009
  • 4. GOODBYE JSPARTY • Merged external libraries to cms/thirdparty and sapphire/thirdparty Friday, 28 August 2009
  • 5. THE DARK AGES function hover_over() { Element.addClassName(this, 'over'); } function hover_out() { Element.removeClassName(this, 'over'); } hover_behaviour = { onmouseover : hover_over, onmouseout : hover_out } jsparty/hover.js Friday, 28 August 2009
  • 6. THE DARK AGES class LeftAndMain { // ... public function returnItemToUser($p) { // ... $response = <<<JS var tree = $('sitetree'); var newNode = tree.createTreeNode("$id", "$treeTitle", "{$p->class}{$hasChildren} {$singleInstanceCSSClass}"); node = tree.getTreeNodeByIdx($parentID); if(!node) { node = tree.getTreeNodeByIdx(0); } node.open(); node.appendTreeNode(newNode); newNode.selectTreeNode(); JS; FormResponse::add($response); FormResponse::add($this->hideSingleInstanceOnlyFromCreateFieldJS($p)); return FormResponse::respond(); } } cms/code/LeftAndMain.php (excerpt) Friday, 28 August 2009
  • 7. BEST PRACTICES • Don’t claim global properties • Assume element collections • Encapsulate: jQuery.concrete, jQuery plugin, jQueryUI widget (in this order) Friday, 28 August 2009
  • 8. ENCAPSULATE: EXAMPLE Simple Highlight jQuery Plugin // create closure (function($) { // plugin definition $.fn.hilight = function(options) { // build main options before element iteration var opts = $.extend({}, $.fn.hilight.defaults, options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // build element specific options var o = $.meta ? $.extend({}, opts, $this.data()) : opts; // update element styles $this.css({backgroundColor: o.background,color: o.foreground}); }); }; // plugin defaults $.fn.hilight.defaults = {foreground: "red",background: "yellow"}; // end of closure })(jQuery); Friday, 28 August 2009
  • 9. BEST PRACTICES • Useplain HTML, jQuery.data() and jQuery.metadata to encode initial state and (some) configuration • Better than building “object cathedrals” in most cases Friday, 28 August 2009
  • 10. STATE: EXAMPLE Simple Form Changetracking State in CSS $('form :input').bind('change', function(e) { $(this.form).addClass('isChanged'); }); properties $('form').bind('submit', function(e) { if($(this).hasClass('isChanged')) return false; }); $('form :input').bind('change', function(e) { State in DOM $(this.form).data('isChanged', true); }); $('form').bind('submit', function(e) { (through jQuery.data()) if($(this).data('isChanged')) return false; }); Friday, 28 August 2009
  • 11. BEST PRACTICES • Ajax responses should default to HTML Makes it easier to write unobtrusive markup and use SilverStripe templating • Use HTTP metadata to transport state and additional data Example: 404 HTTP status code to return “Not Found” Example: Custom “X-Status” header for more detailed UI status • Return JSON if HTML is not feasible Example: Update several tree nodes after CMS batch actions Alternative: Inspect the returned DOM for more structured data like id attributes • For AJAX and parameter transmission, <form> is your friend Friday, 28 August 2009
  • 12. AJAX: EXAMPLE Simple Serverside Autocomplete for Page Titles <form action"#"> <div class="autocomplete {url:'MyController/autocomplete'}"> <input type="text" name="title" /> <div class="results" style="display: none;"> </div> <input type="submit" value="action_autocomplete" /> </form> MyController.ss Using jQuery.metadata, but could be a plain SilverStripe Form as well <ul> <% control Results %> <li id="Result-$ID">$Title</li> <% end_control %> </ul> AutoComplete.ss Friday, 28 August 2009
  • 13. AJAX: EXAMPLE class MyController { function autocomplete($request) { $SQL_title = Convert::raw2sql($request->getVar('title')); $results = DataObject::get("Page", "Title LIKE '%$SQL_title%'"); if(!$results) return new HTTPResponse("Not found", 404); // Use HTTPResponse to pass custom status messages $this->response->setStatusCode(200, "Found " . $results->Count() . " elements"); // render all results with a custom template $vd = new ViewableData(); return $vd->customise(array( "Results" => $results ))->renderWith('AutoComplete'); } } MyController.php Friday, 28 August 2009
  • 14. AJAX: EXAMPLE $('.autocomplete input').live('change', function() { var resultsEl = $(this).siblings('.results'); resultsEl.load( // get form action, using the jQuery.metadata plugin $(this).parent().metadata().url, // submit all form values $(this.form).serialize(), // callback after data is loaded function(data, status) { resultsEl.show(); // optional: get all record IDs from the new HTML var ids = jQuery('.results').find('li').map(function() { return $(this).attr('id').replace(/Record-/,''); }); } ); }); MyController.js Friday, 28 August 2009
  • 15. BEST PRACTICES • Use events and observation for component communication • Use composition for synchronous, two-way communicatoin • Use callbacks to allow for customization • Don’t overuse prototypical inheritance and pseudo-classes • Only expose public APIs where necessary (through jQuery.concrete) Friday, 28 August 2009
  • 16. RESOURCES Documentation (unreleased) http://doc.silverstripe.com/doku.php?id=2.4:javascript Source (unreleased, pre 2.4 alpha) http://github.com/chillu/sapphire/tree/jsrewrite http://github.com/chillu/cms/tree/jsrewrite Friday, 28 August 2009