Outlines the refactoring effort of the SilverStripe CMS codebase, which was an internal project at SilverStripe Ltd. mid 2009. The main objective was to simplify CMS extensions for intermediate frontend/backend developers. In practice this meant a lot of housekeeping, and the consistent usage of jQuery and jQuery UI over PrototypeJS.
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