pointed out why the tutorial reference implementations are there :P
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD.pod
CommitLineData
d442cc9f 1=head1 NAME
2
3Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
4
5
6=head1 OVERVIEW
7
8This is B<Part 8 of 9> for the Catalyst tutorial.
9
10L<Tutorial Overview|Catalyst::Manual::Tutorial>
11
12=over 4
13
14=item 1
15
16L<Introduction|Catalyst::Manual::Tutorial::Intro>
17
18=item 2
19
20L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
21
22=item 3
23
24L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
25
26=item 4
27
28L<Authentication|Catalyst::Manual::Tutorial::Authentication>
29
30=item 5
31
32L<Authorization|Catalyst::Manual::Tutorial::Authorization>
33
34=item 6
35
36L<Debugging|Catalyst::Manual::Tutorial::Debugging>
37
38=item 7
39
40L<Testing|Catalyst::Manual::Tutorial::Testing>
41
42=item 8
43
44B<AdvancedCRUD>
45
46=item 9
47
48L<Appendices|Catalyst::Manual::Tutorial::Appendices>
49
50=back
51
52=head1 DESCRIPTION
53
54This part of the tutorial explores more advanced functionality for
55Create, Read, Update, and Delete (CRUD) than we saw in Part 3. In
56particular, it looks at a number of techniques that can be useful for
57the Update portion of CRUD, such as automated form generation,
58validation of user-entered data, and automated transfer of data between
59forms and model objects.
60
61In keeping with the Catalyst (and Perl) spirit of flexibility, there are
62many different ways to approach advanced CRUD operations in a Catalyst
63environment. One alternative is to use
64L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold>
65to instantly construct a set of Controller methods and templates for
66basic CRUD operations. Although a popular subject in Quicktime
67movies that serve as promotional material for various frameworks,
68real-world applications generally require more control. Other
69options include L<Data::FormValidator|Data::FormValidator> and
70L<HTML::FillInForm|HTML::FillInForm>.
71
72Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only
73ease form creation, but to also provide validation of the submitted
74data. The approached used by this part of the tutorial is to slowly
75incorporate additional L<HTML::Widget|HTML::Widget> functionality in a
76step-wise fashion (we start with fairly simple form creation and then
77move on to more complex and "magical" features such as validation and
78auto-population/auto-saving).
79
80B<Note:> Part 8 of the tutorial is optional. Users who do not wish to
81use L<HTML::Widget|HTML::Widget> may skip this part.
82
83You can checkout the source code for this example from the catalyst subversion repository as per the instructions in L<Catalyst::Manual::Tutorial::Intro>
84
85=head1 C<HTML::WIDGET> FORM CREATION
86
87This section looks at how L<HTML::Widget|HTML::Widget> can be used to
88add additional functionality to the manually created form from Part 3.
89
90=head2 Add the C<HTML::Widget> Plugin
91
92Open C<lib/MyApp.pm> in your editor and add the following to the list of
93plugins (be sure to leave the existing plugins enabled):
94
95 HTML::Widget
96
97=head2 Add a Form Creation Helper Method
98
99Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
100following method:
101
102 =head2 make_book_widget
103
104 Build an HTML::Widget form for book creation and updates
105
106 =cut
107
108 sub make_book_widget {
109 my ($self, $c) = @_;
110
111 # Create an HTML::Widget to build the form
112 my $w = $c->widget('book_form')->method('post');
113
114 # Get authors
115 my @authorObjs = $c->model("MyAppDB::Author")->all();
116 my @authors = map {$_->id => $_->last_name }
117 sort {$a->last_name cmp $b->last_name} @authorObjs;
118
119 # Create the form feilds
120 $w->element('Textfield', 'title' )->label('Title')->size(60);
121 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
122 $w->element('Select', 'authors')->label('Authors')
123 ->options(@authors);
124 $w->element('Submit', 'submit' )->value('submit');
125
126 # Return the widget
127 return $w;
128 }
129
130This method provides a central location that builds an
131HTML::Widget-based form with the appropriate fields. The "Get authors"
132code uses DBIC to retrieve a list of model objects and then uses C<map>
133to create a hash where the hash keys are the database primary keys from
134the authors table and the associated values are the last names of the
135authors.
136
137=head2 Add Actions to Display and Save the Form
138
139Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
140following methods:
141
142 =head2 hw_create
143
144 Build an HTML::Widget form for book creation and updates
145
146 =cut
147
148 sub hw_create : Local {
149 my ($self, $c) = @_;
150
151 # Create the widget and set the action for the form
152 my $w = $self->make_book_widget($c);
153 $w->action($c->uri_for('hw_create_do'));
154
155 # Write form to stash variable for use in template
156 $c->stash->{widget_result} = $w->result;
157
158 # Set the template
159 $c->stash->{template} = 'books/hw_form.tt2';
160 }
161
162
163 =head2 hw_create_do
164
165 Build an HTML::Widget form for book creation and updates
166
167 =cut
168
169 sub hw_create_do : Local {
170 my ($self, $c) = @_;
171
172 # Retrieve the data from the form
173 my $title = $c->request->params->{title};
174 my $rating = $c->request->params->{rating};
175 my $authors = $c->request->params->{authors};
176
177 # Call create() on the book model object. Pass the table
178 # columns/field values we want to set as hash values
179 my $book = $c->model('MyAppDB::Book')->create({
180 title => $title,
181 rating => $rating
182 });
183
184 # Add a record to the join table for this book, mapping to
185 # appropriate author
186 $book->add_to_book_authors({author_id => $authors});
187
188 # Set a status message for the user
189 $c->stash->{status_msg} = 'Book created';
190
191 # Use 'hw_create' to redisplay the form. As discussed in
192 # Part 3, 'detach' is like 'forward', but it does not return
193 $c->detach('hw_create');
194 }
195
196Note how we use C<make_book_widget> to build the core parts of the form
197in one location, but we set the action (the URL the form is sent to when
198the user clicks the 'Submit' button) separately in C<hw_create>. Doing
199so allows us to have the same form submit the data to different actions
200(e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
201update an existing book object).
202
203B<NOTE:> If you receive an error about Catalyst not being able to find
204the template C<hw_create_do.tt2>, please verify that you followed the
205instructions in the final section of
206L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics> where
207you returned to a manually-specified template. You can either use
208C<forward>/C<detach> B<OR> default template names, but the two cannot
209be used together.
210
211
212=head2 Update the CSS
213
214Edit C<root/src/ttsite.css> and add the following lines to the bottom of
215the file:
216
217 label {
218 display: block;
219 width: 10em;
220 position: relative;
221 margin: .5em 0em;
222 }
223 label input {
224 position: absolute;
225 left: 100%;
226 }
227 label select {
228 position: absolute;
229 left: 100%;
230 }
231 .submit {
232 margin-top: 2em;;
233 }
234 .error_messages {
235 color: [% site.col.error %];
236 }
237
238These changes will display form elements vertically and also show error
239messages in red. Note that we are pulling the color scheme settings
240from the C<root/lib/config/col> file that was created by the TTSite
241helper. This allows us to change the color used by various error styles
242in the CSS from a single location.
243
244=head2 Create a Template Page To Display The Form
245
246Open C<root/src/books/hw_form.tt2> in your editor and enter the following:
247
248 [% META title = 'Create/Update Book' %]
249
250 [% widget_result.as_xml %]
251
252 <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
253
254=head2 Add Links for Create and Update via C<HTML::Widget>
255
256Open C<root/src/books/list.tt2> in your editor and add the following to
257the bottom of the existing file:
258
259 <p>
260 HTML::Widget:
261 <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
262 </p>
263
264
265=head2 Test The <HTML::Widget> Create Form
266
267Press C<Ctrl-C> to kill the previous server instance (if it's still
268running) and restart it:
269
270 $ script/myapp_server.pl
271
272Login as C<test01>. Once at the Book List page, click the HTML::Widget
273"Create" link to display for form produced by C<make_book_widget>. Fill
274out the form with the following values: Title = "Internetworking with
275TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit, and
276you will be returned to the Create/Update Book page with a "Book
277created" status message displayed. Click "Return to book list" to view
278the newly created book on the main list.
279
280Also note that this implementation allows you to can create books with
281bogus information. Although we have constrained the authors with the
282drop-down list, there are no restrictions on items such as the length of
283the title (for example, you can create a one-letter title) and value for
284the rating (you can use any number you want, and even non-numeric values
285with SQLite). The next section will address this concern.
286
287B<Note:> Depending on the database you are using and how you established
288the columns in your tables, the database could obviously provide various
289levels of "type enforcement" on your data. The key point being made in
290the previous paragraph is that the I<web application> itself is not
291performing any validation.
292
293=head1 C<HTML::WIDGET> VALIDATION AND FILTERING
294
295Although the use of L<HTML::Widget|HTML::Widget> in the previous section
296did provide an automated mechanism to build the form, the real power of
297this module stems from functionality that can automatically validate and
298filter the user input. Validation uses constraints to be sure that
299users input appropriate data (for example, that the email field of a
300form contains a valid email address). Filtering can be used to remove
301extraneous whitespace from fields or to escape meta-characters in user
302input.
303
304=head2 Add Constraints and Filters to the Widget Creation Method
305
306Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
307C<make_book_widget> method to match the following (new sections have
308been marked with a C<*** NEW:> comment):
309
310 sub make_book_widget {
311 my ($self, $c) = @_;
312
313 # Create an HTML::Widget to build the form
314 my $w = $c->widget('book_form')->method('post');
315
316 # Get authors
317 my @authorObjs = $c->model("MyAppDB::Author")->all();
318 my @authors = map {$_->id => $_->last_name }
319 sort {$a->last_name cmp $b->last_name} @authorObjs;
320
321 # Create the form feilds
322 $w->element('Textfield', 'title' )->label('Title')->size(60);
323 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
324 # ***NEW: Convert to multi-select list
325 $w->element('Select', 'authors')->label('Authors')
326 ->options(@authors)->multiple(1)->size(3);
327 $w->element('Submit', 'submit' )->value('submit');
328
329 # ***NEW: Set constraints
330 $w->constraint(All => qw/title rating authors/)
331 ->message('Required. ');
332 $w->constraint(Integer => qw/rating/)
333 ->message('Must be an integer. ');
334 $w->constraint(Range => qw/rating/)->min(1)->max(5)
335 ->message('Must be a number between 1 and 5. ');
336 $w->constraint(Length => qw/title/)->min(5)->max(50)
337 ->message('Must be between 5 and 50 characters. ');
338
339 # ***NEW: Set filters
340 for my $column (qw/title rating authors/) {
341 $w->filter( HTMLEscape => $column );
342 $w->filter( TrimEdges => $column );
343 }
344
345 # Return the widget
346 return $w;
347 }
348
349The main changes are:
350
351=over 4
352
353=item *
354
355The C<Select> element for C<authors> is changed from a single-select
356drop-down to a multi-select list by adding calls to C<multiple> (set to
357C<true>) and C<size> (set to the number of rows to display).
358
359=item *
360
361Four sets of constraints are added to provide validation of the user input.
362
363=item *
364
365Two filters are run on every field to remove and escape unwanted input.
366
367=back
368
369=head2 Rebuild the Form Submission Method to Include Validation
370
371Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
372match the following code (enough of the code is different that you
373probably want to cut and paste this over code the existing method):
374
375 sub hw_create_do : Local {
376 my ($self, $c) = @_;
377
378 # Retrieve the data from the form
379 my $title = $c->request->params->{title};
380 my $rating = $c->request->params->{rating};
381 my $authors = $c->request->params->{authors};
382
383 # Create the widget and set the action for the form
384 my $w = $self->make_book_widget($c);
385 $w->action($c->uri_for('hw_create_do'));
386
387 # Validate the form parameters
388 my $result = $w->process($c->req);
389
390 # Write form (including validation error messages) to
391 # stash variable for use in template
392 $c->stash->{widget_result} = $result;
393
394 # Were their validation errors?
395 if ($result->has_errors) {
396 # Warn the user at the top of the form that there were errors.
397 # Note that there will also be per-field feedback on
398 # validation errors because of '$w->process($c->req)' above.
399 $c->stash->{error_msg} = 'Validation errors!';
400 } else {
401 # Everything validated OK, so do the create
402 # Call create() on the book model object. Pass the table
403 # columns/field values we want to set as hash values
404 my $book = $c->model('MyAppDB::Book')->create({
405 title => $title,
406 rating => $rating
407 });
408
409 # Add a record to the join table for this book, mapping to
410 # appropriate author. Note that $authors will be 1 author as
411 # a scalar or ref to list of authors depending on how many the
412 # user selected; the 'ref $authors ?...' handles both cases
413 foreach my $author (ref $authors ? @$authors : $authors) {
414 $book->add_to_book_authors({author_id => $author});
415 }
416 # Set a status message for the user
417 $c->stash->{status_msg} = 'Book created';
418 }
419
420 # Set the template
421 $c->stash->{template} = 'books/hw_form.tt2';
422 }
423
424The key changes to C<hw_create_do> are:
425
426=over 4
427
428=item *
429
430C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
431the form. Now that C<hw_create_do> has to process the form in order to
432perform the validation, we go ahead and build a complete set of form
433presentation logic into C<hw_create_do> (for example, C<hw_create_do>
434now has a C<$c-E<gt>stash-E<gt>{template}> line). Note that if we
435process the form in C<hw_create_do> I<and> forward/detach back to
436<hw_create>, we would end up with C<make_book_widget> being called
437twice, resulting in a duplicate set of elements being added to the form.
438(There are other ways to address the "duplicate form rendering" issue --
439just be aware that it exists.)
440
441=item *
442
443C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
444Not only does this set the C<has_errors> flag if validation errors are
445encountered, it returns a string containing any field-specific warning
446messages.
447
448=item *
449
450An C<if> statement checks if any validation errors were encountered. If
451so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
452redisplayed. If no errors were found, the object is created in a manner
453similar to the prior version of the C<hw_create_do> method.
454
455=back
456
457=head2 Try Out the Form
458
459Press C<Ctrl-C> to kill the previous server instance (if it's still
460running) and restart it:
461
462 $ script/myapp_server.pl
463
464Now try adding a book with various errors: title less than 5 characters,
465non-numeric rating, a rating of 0 or 6, etc. Also try selecting one,
466two, and zero authors. When you click Submit, the HTML::Widget
467C<constraint> items will validate the logic and insert feedback as
468appropriate.
469
470
471=head1 Enable C<DBIx::Class::HTMLWidget> Support
472
473In this section we will take advantage of some of the "auto-population"
474features of C<DBIx::Class::HTMLWidget>. Enabling
475C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
476model classes:
477
478=over 4
479
480=item *
481
482fill_widget()
483
484Takes data from the database and transfers it to your form widget.
485
486=item *
487
488populate_from_widget()
489
490Takes data from a form widget and uses it to update the corresponding
491records in the database.
492
493=back
494
495In other words, the two methods are a mirror image of each other: one
496reads from the database while the other writes to the database.
497
498=head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
499
500In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
501need to add C<HTMLWidget> to the C<load_components> line of DBIC result
502source files that need to use the C<fill_widget> and
503C<populate_from_widget> methods. In this case, open
504C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
505
506 __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
507
508=head2 Use C<populate_from_widget> in C<hw_create_do>
509
510Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
511match the following code:
512
513 =head2 hw_create_do
514
515 Build an HTML::Widget form for book creation and updates
516
517 =cut
518
519 sub hw_create_do : Local {
520 my ($self, $c) = @_;
521
522 # Create the widget and set the action for the form
523 my $w = $self->make_book_widget($c);
524 $w->action($c->uri_for('hw_create_do'));
525
526 # Validate the form parameters
527 my $result = $w->process($c->req);
528
529 # Write form (including validation error messages) to
530 # stash variable for use in template
531 $c->stash->{widget_result} = $result;
532
533 # Were their validation errors?
534 if ($result->has_errors) {
535 # Warn the user at the top of the form that there were errors.
536 # Note that there will also be per-field feedback on
537 # validation errors because of '$w->process($c->req)' above.
538 $c->stash->{error_msg} = 'Validation errors!';
539 } else {
540 my $book = $c->model('MyAppDB::Book')->new({});
541 $book->populate_from_widget($result);
542
543 # Add a record to the join table for this book, mapping to
544 # appropriate author. Note that $authors will be 1 author as
545 # a scalar or ref to list of authors depending on how many the
546 # user selected; the 'ref $authors ?...' handles both cases
547 my $authors = $c->request->params->{authors};
548 foreach my $author (ref $authors ? @$authors : $authors) {
549 $book->add_to_book_authors({author_id => $author});
550 }
551
552 # Set a status message for the user
553 $c->flash->{status_msg} = 'Book created';
554
555 # Redisplay an empty form for another
556 $c->stash->{widget_result} = $w->result;
557 }
558
559 # Set the template
560 $c->stash->{template} = 'books/hw_form.tt2';
561 }
562
563In this version of C<hw_create_do> we removed the logic that manually
564pulled the form variables and used them to call
565C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
566single call to C<$book-E<gt>populate_from_widget>. Note that we still
567have to call C<$book-E<gt>add_to_book_authors> once per author because
568C<populate_from_widget> does not currently handle the relationships
569between tables. Also, we reset the form to an empty fields by adding
570another call to C<$w-E<gt>result> and storing the output in the stash
571(if we don't override the output from C<$w-E<gt>process($c-E<gt>req)>,
572the form values already entered will be retained on redisplay --
573although this could be desirable for some applications, we avoid it
574here to help avoid the creation of duplicate records).
575
576
577=head2 Try Out the Form
578
579Press C<Ctrl-C> to kill the previous server instance (if it's still
580running) and restart it:
581
582 $ script/myapp_server.pl
583
584Try adding a book that validates. Return to the book list and the book
585you added should be visible.
586
587
588
589=head1 Rendering C<HTMLWidget> Forms in a Table
590
591Some developers my wish to use the "old-fashioned" table style of
592rendering a form in lieu of the default C<HTML::Widget> rendering that
593assumes you will use CSS for formatting. This section demonstrates
594some techniques that can override the default rendering with a
595custom class.
596
597
598=head2 Add a New "Element Container"
599
600Open C<lib/FormElementContainer.pm> in your editor and enter:
601
602 package FormElementContainer;
603
604 use base 'HTML::Widget::Container';
605
606 sub _build_element {
607 my ($self, $element) = @_;
608
609 return () unless $element;
610 if (ref $element eq 'ARRAY') {
611 return map { $self->_build_element($_) } @{$element};
612 }
613 my $e = $element->clone;
614 $e = new HTML::Element('span', class => 'fields_with_errors')->push_content($e)
615 if $self->error && $e->tag eq 'input';
616
617 return $e ? ($e) : ();
618 }
619
620 1;
621
622This simply dumps the HTML code for a given form element, followed by a
623C<span> that can contain validation error message.
624
625
626=head2 Enable the New Element Container When Building the Form
627
628Open C<lib/MyApp/Controller/Books.pm> in your editor. First add a
629C<use> for your element container class:
630
631 use FormElementContainer;
632
633B<Note:> If you forget to C<use> your container class in your
634controller, then your form will not be displayed and no error messages
635will be generated. Don't forget this important step!
636
637Then tell C<HTML::Widget> to use that class during rendering by updating
638C<make_book_widget> to match the following:
639
640 sub make_book_widget {
641 my ($self, $c) = @_;
642
643 # Create an HTML::Widget to build the form
644 my $w = $c->widget('book_form')->method('post');
645
646 # ***New: Use custom class to render each element in the form
647 $w->element_container_class('FormElementContainer');
648
649 # Get authors
650 my @authorObjs = $c->model("MyAppDB::Author")->all();
651 my @authors = map {$_->id => $_->last_name }
652 sort {$a->last_name cmp $b->last_name} @authorObjs;
653
654 # Create the form feilds
655 $w->element('Textfield', 'title' )->label('Title')->size(60);
656 $w->element('Textfield', 'rating' )->label('Rating')->size(1);
657 # Convert to multi-select list
658 $w->element('Select', 'authors')->label('Authors')
659 ->options(@authors)->multiple(1)->size(3);
660 $w->element('Submit', 'submit' )->value('submit');
661
662 # Set constraints
663 $w->constraint(All => qw/title rating authors/)
664 ->message('Required. ');
665 $w->constraint(Integer => qw/rating/)
666 ->message('Must be an integer. ');
667 $w->constraint(Range => qw/rating/)->min(1)->max(5)
668 ->message('Must be a number between 1 and 5. ');
669 $w->constraint(Length => qw/title/)->min(5)->max(50)
670 ->message('Must be between 5 and 50 characters. ');
671
672 # Set filters
673 for my $column (qw/title rating authors/) {
674 $w->filter( HTMLEscape => $column );
675 $w->filter( TrimEdges => $column );
676 }
677
678 # Return the widget
679 return $w;
680 }
681
682The two new lines are marked with C<***New:>.
683
684
685=head2 Update the TT Template
686
687Open C<root/src/books/hw_form.tt2> and edit it to match:
688
689 [% META title = 'Create/Update Book' %]
690
691 [%# Comment out the auto-rendered form %]
692 [%# widget_result.as_xml %]
693
694
695 [%# Iterate over the form elements and display each -%]
696 <form name="book_form" action="[% widget_result.action %]" method="post">
697 <table border="0">
698 [% FOREACH element = widget_result.elements %]
699 <tr>
700 <td class="form-label">
701 [% element.label.as_text %]
702 </td>
703 <td class="form-element">
704 [% element.element_xml %]
705 <span class="form-error">
706 [% element.error_xml %]
707 </span>
708 </td>
709 </tr>
710 [% END %]
711 </table>
712 </form>
713
714
715 <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
716
717
718 [%# A little JavaScript to move the cursor to the first field %]
719 <script LANGUAGE="JavaScript">
720 document.book_form.book_form_title.focus();
721 </script>
722
723This represents three changes:
724
725=over 4
726
727=item *
728
729The existing C<widget_result.as_xml> has been commented out.
730
731=item *
732
733It loops through each form element, displaying the field name in the
734first table cell along with the form element and validation errors in
735the second field.
736
737=item *
738
739JavaScript to position the user's cursor in the first field of the form.
740
741=back
742
743
744=head2 Try Out the Form
745
746Press C<Ctrl-C> to kill the previous server instance (if it's still
747running) and restart it:
748
749 $ script/myapp_server.pl
750
751Try adding a book that validates. Return to the book list and the book
752you added should be visible.
753
754
755=head1 AUTHOR
756
757Kennedy Clark, C<hkclark@gmail.com>
758
759Please report any errors, issues or suggestions to the author. The
760most recent version of the Catalyst Tutorial can be found at
761L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
762
763Copyright 2006, Kennedy Clark, under Creative Commons License
764(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).