You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2014/11/18 22:25:47 UTC
svn commit: r1640429 - in /isis/site/trunk/content: documentation.md
reference/recognized-annotations/about.md tutorials/apacheconeu-2014.md
Author: danhaywood
Date: Tue Nov 18 21:25:46 2014
New Revision: 1640429
URL: http://svn.apache.org/r1640429
Log:
apachecon eu tutorial
Modified:
isis/site/trunk/content/documentation.md
isis/site/trunk/content/reference/recognized-annotations/about.md
isis/site/trunk/content/tutorials/apacheconeu-2014.md
Modified: isis/site/trunk/content/documentation.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/documentation.md?rev=1640429&r1=1640428&r2=1640429&view=diff
==============================================================================
--- isis/site/trunk/content/documentation.md (original)
+++ isis/site/trunk/content/documentation.md Tue Nov 18 21:25:46 2014
@@ -53,7 +53,7 @@ Title: Documentation
<p class="display:none"/>
- [Downloadable Presentations](intro/resources/downloadable-presentations.html)
-- **[IDE templates**](intro/resources/editor-templates.html)** (IntelliJ and Eclipse)
+- **[IDE templates](intro/resources/editor-templates.html)** (IntelliJ and Eclipse)
- [Icons](intro/resources/icons.html)
- **[Cheat Sheet](intro/resources/cheat-sheet.html)**
Modified: isis/site/trunk/content/reference/recognized-annotations/about.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/reference/recognized-annotations/about.md?rev=1640429&r1=1640428&r2=1640429&view=diff
==============================================================================
--- isis/site/trunk/content/reference/recognized-annotations/about.md (original)
+++ isis/site/trunk/content/reference/recognized-annotations/about.md Tue Nov 18 21:25:46 2014
@@ -19,7 +19,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./ActionOrder-deprecated.html">@ActionOrder</a></td>
+ <td><a href="./ActionOrder-deprecated.html" style="text-decoration: line-through;" >@ActionOrder</a></td>
<td>UI</td>
<td>Order of buttons and menu items representing actions.
</td>
@@ -118,7 +118,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./Debug-deprecated.html">@Debug</a></td>
+ <td><a href="./Debug-deprecated.html" style="text-decoration: line-through;" >@Debug</a></td>
<td>UI</td>
<td>Action only invocable in debug mode.
</td>
@@ -174,7 +174,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./Exploration-deprecated.html">@Exploration</a></td>
+ <td><a href="./Exploration-deprecated.html" style="text-decoration: line-through;" >@Exploration</a></td>
<td>UI</td>
<td>Action available in special 'exploration' mode.
</td>
@@ -190,7 +190,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./FieldOrder-deprecated.html">@FieldOrder</a></td>
+ <td><a href="./FieldOrder-deprecated.html" style="text-decoration: line-through;" >@FieldOrder</a></td>
<td>UI</td>
<td>Order of properties and collections
</td>
@@ -215,7 +215,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./Idempotent-deprecated.html">@Idempotent</a></td>
+ <td><a href="./Idempotent-deprecated.html" style="text-decoration: line-through;" >@Idempotent</a></td>
<td>Domain</td>
<td>Replaced by @ActionSemantics.
</td>
@@ -223,7 +223,7 @@ go back to: [documentation](../../docume
<td>Y</td>
</tr>
<tr>
- <td><a href="./Ignore-deprecated.html">@Ignore</a></td>
+ <td><a href="./Ignore-deprecated.html" style="text-decoration: line-through;" >@Ignore</a></td>
<td>Domain</td>
<td>Replaced by @Programmatic.
</td>
@@ -405,7 +405,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./PostsPropertyChangedEvent-deprecated.html">@PostsPropertyChangedEvent</a></td>
+ <td><a href="./PostsPropertyChangedEvent-deprecated.html" style="text-decoration: line-through;" >@PostsPropertyChangedEvent</a></td>
<td>Domain</td>
<td>Replaced by @PropertyInteraction
</td>
@@ -456,7 +456,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./QueryOnly-deprecated.html">@QueryOnly</a></td>
+ <td><a href="./QueryOnly-deprecated.html" style="text-decoration: line-through;" >@QueryOnly</a></td>
<td>Domain</td>
<td>Replaced by @ActionSemantics.
</td>
@@ -496,7 +496,7 @@ go back to: [documentation](../../docume
<td></td>
</tr>
<tr>
- <td><a href="./Resolve-deprecated.html">@Resolve</a></td>
+ <td><a href="./Resolve-deprecated.html" style="text-decoration: line-through;" >@Resolve</a></td>
<td>UI</td>
<td>Replaced by @Render.
</td>
Modified: isis/site/trunk/content/tutorials/apacheconeu-2014.md
URL: http://svn.apache.org/viewvc/isis/site/trunk/content/tutorials/apacheconeu-2014.md?rev=1640429&r1=1640428&r2=1640429&view=diff
==============================================================================
--- isis/site/trunk/content/tutorials/apacheconeu-2014.md (original)
+++ isis/site/trunk/content/tutorials/apacheconeu-2014.md Tue Nov 18 21:25:46 2014
@@ -1,6 +1,8 @@
Title: Stop scaffolding, start coding
+{note
A half-day tutorial on developing domain-driven apps using Apache Isis.
+}
## Run the archetype
@@ -28,7 +30,7 @@ or alternatively
mvn jetty:run
-####DEMO
+## Using the app
* install fixtures
* list all
@@ -38,18 +40,20 @@ or alternatively
##Dev environment
+Set up an IDE and import the project to be able to run and debug the app
+
#### Configure
* IDE:
- * [IntelliJ](http://isis.apache.org/intro/getting-started/ide/intellij.html)
- * [Eclipse](http://isis.apache.org/intro/getting-started/ide/eclipse.html)
-* [IDE Editor templates](http://isis.apache.org/intro/resources/editor-templates.html)
+ * configure [IntelliJ](http://isis.apache.org/intro/getting-started/ide/intellij.html), import app
+ * configure [Eclipse](http://isis.apache.org/intro/getting-started/ide/eclipse.html), import app
+* Set up IDE [editor templates](http://isis.apache.org/intro/resources/editor-templates.html)
#### Run
* Run the app from within the IDE
-* Run with different deploymentTypes:
+* Run with different deploymentTypes, note whether `@Prototype` actions are available or not:
- `--type SERVER_PROTOTYPE`
- `--type SERVER`
@@ -57,13 +61,14 @@ or alternatively
## Explore codebase
* `myapp` : parent module
-* `myapp-dom`
- - `dom.simple.SimpleObject`
- - `dom.simple.SimpleObjects`
-* `myapp-fixture`
- - `fixture.simple.SimpleObjectsFixture`
-* `myapp-integtests`
-* `myapp-webapp`
+* `myapp-dom`: domain objects module
+ - entity: `dom.simple.SimpleObject`
+ - repository: `dom.simple.SimpleObjects`
+* `myapp-fixture`: fixtures module
+ - fixture script:`fixture.simple.SimpleObjectsFixture`
+* `myapp-integtests`: integration tests module
+* `myapp-webapp`: webapp module
+ * (builds the WAR file)
## Testing
@@ -113,6 +118,7 @@ The remainder of the tutorial provides g
![](http://yuml.me/a070d071)
+which in yuml.me's DSL is:
<pre>
[Visit|-checkIn:DateTime;-checkout:DateTime;-diagnosis:String|+checkin();+checkout();+addNote()]->[Pet|-name:String;-species:PetSpecies]
[Owner|-firstName:String;-lastName:String]<0..1-0..*>[Pet]
@@ -122,7 +128,7 @@ The remainder of the tutorial provides g
## Domain class
* rename the `SimpleObject` class
-* rename the `name` property
+* rename the `SimpleObject` class' `name` property
* specify a [title](http://isis.apache.org/how-tos/how-to-01-040-How-to-specify-a-title-for-a-domain-entity.html)
* specify an [icon](http://isis.apache.org/how-tos/how-to-01-070-How-to-specify-the-icon-for-a-domain-entity.html)
@@ -147,7 +153,8 @@ The remainder of the tutorial provides g
## Actions
-* update the domain property (`SimpleObject#name` above, renamed by now)
+* update the domain action (`SimpleObject#name` above, renamed by now)
+* use the [@Named](http://isis.apache.org/reference/recognized-annotations/Named.html) annotation to specify the name of action parameters
* use [@ActionSemantics](http://isis.apache.org/reference/recognized-annotations/ActionSemantics.html) annotation to indicate the semantics of the action (safe/query-only, idempotent or non-idempotent)
* annotate safe action as [@Bookmarkable](http://isis.apache.org/reference/recognized-annotations/Bookmarkable.html)
* confirm is available from bookmark panel (top-left of Wicket UI)
@@ -156,8 +163,8 @@ The remainder of the tutorial provides g
## REST API
* add Chrome extensions
- * install Postman
- * install JSON-View
+ * install [Postman](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en)
+ * install [JSON-View](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc?hl=en)
* browse to Wicket viewer, install fixtures
* browse to the http://localhost:8080/restful API
* invoke the service to list all objects
@@ -165,19 +172,25 @@ The remainder of the tutorial provides g
## Specify Action semantics
-* note the HTTP methods exposed in the REST API
+* experiment changing [@ActionSemantics] on actions
+ * note the HTTP methods exposed in the REST API change
## Value properties
* add some [value properties](http://isis.apache.org/how-tos/how-to-01-030-How-to-add-a-property-to-a-domain-entity.html); also:
- - [optional vs mandatory](http://isis.apache.org/components/objectstores/jdo/mapping-mandatory-and-optional-properties.html)
- - [joda date/time](http://isis.apache.org/components/objectstores/jdo/mapping-joda-dates.html)
- - [bigdecimals](http://isis.apache.org/components/objectstores/jdo/mapping-bigdecimals.html)
- - [blob/clobs](http://isis.apache.org/components/objectstores/jdo/mapping-blobs.html)
-* update the corresponding domain service
- - if not optional
-* update the title, if need be
+ - for string properties
+ - use the [@Multiline](http://isis.apache.org/reference/recognized-annotations/MultiLine.html) annotation to render a text area instead of a text box
+ - use the [@MaxLength](http://isis.apache.org/reference/recognized-annotations/MaxLength.html) annotation to specify the maximum number of characters allowable
+ - use [joda date/time](http://isis.apache.org/components/objectstores/jdo/mapping-joda-dates.html) properties
+ - use [bigdecimals](http://isis.apache.org/components/objectstores/jdo/mapping-bigdecimals.html) properties
+ - use [blob/clobs](http://isis.apache.org/components/objectstores/jdo/mapping-blobs.html) properties
+ - specify whether [optional or mandatory](http://isis.apache.org/components/objectstores/jdo/mapping-mandatory-and-optional-properties.html)
+* TODO: enums
+* update the corresponding domain service for creating new instances
+ - for all non-optional properties will either need to prompt for a value, or calculate some suitable default
+* change the implementation of title, if need be
+ - might prefer to use [@Title](http://isis.apache.org/reference/recognized-annotations/Title.html) annotation rather than the `title()` method
* [order the properties](http://isis.apache.org/how-tos/how-to-01-080-How-to-specify-the-order-in-which-properties-or-collections-are-displayed.html) using the [@MemberOrder](http://isis.apache.org/reference/recognized-annotations/MemberOrder.html) annotation and [@MemberGroupLayout](http://isis.apache.org/reference/recognized-annotations/MemberGroupLayout.html) annotation
* see also this [static layouts](http://isis.apache.org/components/viewers/wicket/static-layouts.html) documentation
@@ -200,13 +213,14 @@ The remainder of the tutorial provides g
## Collections
-* Ensure that all domain classes implement `Comparable`
+* Ensure that all domain classes implement `java.lang.Comparable`
* use the [ObjectContracts](http://isis.apache.org/reference/Utility.html) utility class to help implement `Comparable` (also `equals()`, `hashCode()`, `toString()`)
* Add a [one-to-many-collection](http://isis.apache.org/components/objectstores/jdo/managed-1-to-m-relationships.html) to one of the entities
* Use `SortedSet` as the class
-* TODO: @Render (http://isis.apache.org/reference/recognized-annotations/Render.html)
+* Use the @Render (http://isis.apache.org/reference/recognized-annotations/Render.html) annotation to indicate if the collection should be visible or hidden by default
* optional: Use the [@SortedBy](http://isis.apache.org/reference/recognized-annotations/SortedBy.html) annotation to specify a different comparator than the natural ordering
+
## Actions (ctd)
* Add domain actions to add/remove from the collection
@@ -219,26 +233,28 @@ The remainder of the tutorial provides g
* Delete the `@MemberOrder` annotations and use the associated [.layout.json](http://isis.apache.org/components/viewers/wicket/dynamic-layouts.html) file to specify layout hints instead
-## Business rules: See it!
+## Business rules
+
+### See it!
* Use the [@Hidden](http://isis.apache.org/reference/recognized-annotations/Hidden.html) annotation to make properties/collections/actions invisible
* the [@Programmatic](http://isis.apache.org/reference/recognized-annotations/Programmatic.html) annotation can also be used and in many cases is to be preferred; the difference is that the latter means the member is not part of the Isis metamodel.
* Use the `hideXxx()` supporting method on [properties](http://isis.apache.org/how-tos/how-to-02-010-How-to-hide-a-property.html), [collections](http://isis.apache.org/how-tos/how-to-02-020-How-to-hide-a-collection.html) and [actions](http://isis.apache.org/how-tos/how-to-02-030-How-to-hide-an-action.html) to make a property/collection/action invisible according to some imperative rule
-## Business rules: Use it!
+### Use it!
* Use the [@Disabled](http://isis.apache.org/reference/recognized-annotations/Disabled.html) annotation to make properties read-only/actions non-invokable ('greyed out')
* Use the `disabledXxx()` supporting method on [properties](http://isis.apache.org/how-tos/how-to-02-050-How-to-prevent-a-property-from-being-modified.html) and [actions](http://isis.apache.org/how-tos/how-to-02-070-How-to-prevent-an-action-from-being-invoked.html) to make a property/action disabled according to some imperative rule
-## Business rules: Do it!
-
-* Use the [@Regex](http://isis.apache.org/reference/recognized-annotations/RegEx.html) annotation on string properties or action parameters to validate strings
-* Use the [@MinLength]()
+### Do it!
-
-(or more generally the [@MustSatisfy]() annotation) to specify
+* Validate string properties or action paramters:
+ - use the [@Regex](http://isis.apache.org/reference/recognized-annotations/RegEx.html) annotation to specify a pattern
+ - use the [@MinLength](http://isis.apache.org/reference/recognized-annotations/MinLength.html) annotation to indicate a minimum number of characters
+* For any data type:
+ - use the [@MustSatisfy](http://isis.apache.org/reference/recognized-annotations/MustSatisfy.html) annotation to specify an arbitrary constraint
Use the `validateXxx()` supporting method on [properties](
@@ -273,6 +289,10 @@ clockservice
.layout.json
+CSS
+
+@HomePage
+
view models
@@ -311,1558 +331,5 @@ composite fixture scripts (a la Estatio)
+integration tests
-
-
-
-
-
-
-
-
-
-
-
-
-## Stuff to do...
-
-* rename domain class
-
-* rename domain service
-
-* fixture scripts
-
-
-Tests
-
-* delete the BDD specs
-
-
-
-## Dele
-
-
-
-##Rename domain class, domain service, fixtures
-
-####SimpleObject
-
-rename to `ConferenceSession`
-
-####SimpleObjects
-
-rename to `ConferenceSessions`
-
-####SimpleObjectsFixture
-
-rename to `ConferenceSessionsFixture`
-
-
-####myapp-webapp/src/main/webapp/WEB-INF/isis.properties
-
-<pre>
-isis.services = \
- 10:dom.simple.SimpleObjects,\
- ...
-
-isis.fixtures=fixture.simple.SimpleObjectsFixture
-</pre>
-
-to
-
-<pre>
-isis.services = \
- 10:dom.simple.ConferenceSessions,\
- ...
-
-isis.fixtures=fixture.simple.ConferenceSessionsFixture
-</pre>
-
-####ConferenceSessionsFixture
-
-<pre>
-isisJdoSupport.executeUpdate("delete from \"SimpleObject\"");
-</pre>
-
-to:
-
-<pre>
-isisJdoSupport.executeUpdate("delete from \"ConferenceSession\"");
-</pre>
-
-#### icon
-
-* add icon, `icons/OverheadProjector.png` -> `myapp/dom/src/main/resources/images/ConferenceSession.png`
-
-
-
-####ConferenceSessions
-
-* getId()
-
-*a "nice to have", but has an effect on the URLs exposed in the REST API*
-
-<pre>
-public String getId() {
- return "conferenceSessions";
-}
-</pre>
-
-* `iconName()`
-
-<pre>
-public String iconName() {
- return "ConferenceSession";
-}
-</pre>
-
-####ConferenceSession
-
-*a "nice to have", but has an effect on the URLs exposed in the REST API*
-<pre>
-@ObjectType("SESSION")
-</pre>
-
-
-####DEMO
-
-* menu service changed to "Conference Sessions"
-* tooltip on icon/title changed to "Conference Session"
-* icon changed
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-030`
-
-
-
-
-
-
-##Refactor domain class
-
-####ConferenceSession
-
-* rename `name` property to `sessionTitle`
-
-* update `compareTo()`
-<pre>
-public int compareTo(ConferenceSession other) {
- return ObjectContracts.compare(this, other, "sessionTitle");
-}
-</pre>
-
-* review title
-
-<pre>
-@Title
-public String getSessionTitle() {
- return sessionTitle;
-}
-</pre>
-
-#### ConferenceSessionsFixture
-
-* Better fixture data
-
-<pre>
-private void installObjects() {
- create("RRRADDD! Apache Isis");
- create("Best practices for AngularJS");
- create("Refactor your specs!");
- create("Why Kotlin?");
- create("Introduction to the Play Framework");
- create("Object Oriented Design in the Wild");
-}
-</pre>
-
-
-####DEMO
-
-* table shows sessionTitle column
-* object form shows sessionTitle property
-* object page labelled with title
-* updated data
-
-also
-
-* discuss injected services into fixtures, entities, services etc.
-
-#####Checkpoint:
-`git reset --hard checkpoint-040`
-
-
-
-
-
-##Add date scalar property
-
-####ConferenceSession
-
-<pre>
-// //////////////////////////////////////
-// date (property)
-// //////////////////////////////////////
-
-private LocalDate date;
-
-@javax.jdo.annotations.Persistent
-@javax.jdo.annotations.Column(allowsNull = "true")
-@MemberOrder(sequence = "1")
-public LocalDate getDate() {
- return date;
-}
-
-public void setDate(final LocalDate date) {
- this.date = date;
-}
-</pre>
-
-
-####ConferenceSessionsFixture
-
-<pre>
-private ConferenceSession create(final String name) {
- ...
- session.setDate(clockService.now().plusDays((int)(Math.random()*5)));
- ...
-}
-</pre>
-
-and
-
-<pre>
-private ClockService clockService;
-
-public final void injectClockService(final ClockService clockService) {
- this.clockService = clockService;
-}
-</pre>
->
-
-
-####DEMO
-
-* now has a date property in title and in form
-* can edit, obviously
-* is optional
-
-#####Checkpoint:
-`git reset --hard checkpoint-050`
-
-
-
-
-## Add enum scalar property
-
-* type property
-
-#####ConferenceSession
-
-<pre>
-// //////////////////////////////////////
-// Type (property)
-// //////////////////////////////////////
-
-public enum Type {
- KEYNOTE, ALL_DAY_TUTORIAL, SESSION, OTHER
-}
-
-private Type Type;
-@javax.jdo.annotations.Column(allowsNull="false")
-@MemberOrder(sequence = "1")
-public Type getType() {
- return Type;
-}
-public void setType(final Type Type) {
- this.Type = Type;
-}
-</pre>
-
-
-#####ConferenceSessions
-
-* update the `create(...)` method
-
-<pre>
-public ConferenceSession create(
- final @Named("Name") String name,
- final @Named("Type") ConferenceSession.Type type) {
- final ConferenceSession obj = newTransientInstance(ConferenceSession.class);
- obj.setSessionTitle(name);
- obj.setType(type);
- persistIfNotAlready(obj);
- return obj;
-}
-</pre>
-
-
-#####ConferenceSessionsFixture
-
-* update the fixture data
-
-<pre>
-private void installObjects() {
- create("RRRADDD! Apache Isis", ConferenceSession.Type.SESSION);
- create("Best practices for AngularJS", ConferenceSession.Type.SESSION);
- create("Refactor your specs!", ConferenceSession.Type.SESSION);
- create("Why Kotlin?", ConferenceSession.Type.OTHER);
- create("Introduction to the Play Framework", ConferenceSession.Type.SESSION);
- create("Object Oriented Design in the Wild", ConferenceSession.Type.ALL_DAY_TUTORIAL);
-}
-
-private ConferenceSession create(final String name, final Type type) {
- return simpleObjects.create(name, type);
-}
-</pre>
-
-
-####DEMO
-
-* list all
- * table with new property
-* show object form
-* edit object details
- * both are mandatory
-* create new object
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-060`
-
-
-
-##UI hints
-
-##### `ConferenceSession
-
-* sessionTitle, then type, then date
-
-<pre>
-@MemberOrder(sequence="1")
-public String getSessionTitle() {
-</pre>
-
-* then type
-
-<pre>
-@MemberOrder(sequence = "2")
-public Type getType() {
-</pre>
-
-* then date (hide in table)
-
-<pre>
-@Hidden(where=Where.ALL_TABLES)
-@MemberOrder(name="Scheduling", sequence = "3")
-public LocalDate getDate() {
-</pre>
-
-
-####DEMO
-
-* table shows only title, type properties (in that order)
-* order form shows
- * title, type properties grouped together in the default 'General' group
- * date property separately in a new 'Scheduling' group
-
-#####Checkpoint:
-`git reset --hard checkpoint-070`
-
-
-
-Add another type (and domain service)
--------------------------------------
-
-####Speaker
-
- package dom.simple;
-
- import javax.jdo.annotations.IdentityType;
- import javax.jdo.annotations.VersionStrategy;
-
- import org.apache.isis.applib.DomainObjectContainer;
- import org.apache.isis.applib.annotation.Bookmarkable;
- import org.apache.isis.applib.annotation.MemberOrder;
- import org.apache.isis.applib.annotation.ObjectType;
- import org.apache.isis.applib.annotation.Title;
- import org.apache.isis.applib.util.ObjectContracts;
-
- @javax.jdo.annotations.PersistenceCapable(identityType=IdentityType.DATASTORE)
- @javax.jdo.annotations.DatastoreIdentity(
- strategy=javax.jdo.annotations.IdGeneratorStrategy.IDENTITY,
- column="id")
- @javax.jdo.annotations.Version(
- strategy=VersionStrategy.VERSION_NUMBER,
- column="version")
- @ObjectType("SPEAKER")
- @Bookmarkable
- public class Speaker implements Comparable<Speaker> {
-
-
- // //////////////////////////////////////
- // givenName (property)
- // //////////////////////////////////////
-
- private String givenName;
-
- @MemberOrder(sequence = "1")
- @javax.jdo.annotations.Column(allowsNull="false")
- @Title(sequence="2", prepend=", ")
- public String getGivenName() {
- return givenName;
- }
-
- public void setGivenName(final String name) {
- this.givenName = name;
- }
-
-
- // //////////////////////////////////////
- // familyName (property)
- // //////////////////////////////////////
-
- private String familyName;
-
- @MemberOrder(sequence = "2")
- @javax.jdo.annotations.Column(allowsNull="false")
- @Title(sequence="1")
- public String getFamilyName() {
- return familyName;
- }
-
- public void setFamilyName(final String familyName) {
- this.familyName = familyName;
- }
-
-
- // //////////////////////////////////////
- // compareTo
- // //////////////////////////////////////
-
- @Override
- public int compareTo(Speaker other) {
- return ObjectContracts.compare(this, other, "givenName, familyName");
- }
-
-
- // //////////////////////////////////////
- // Injected
- // //////////////////////////////////////
-
- @SuppressWarnings("unused")
- private DomainObjectContainer container;
- public void injectDomainObjectContainer(final DomainObjectContainer container) {
- this.container = container;
- }
-
- }
-
-
-####Speakers
-
- package dom.simple;
-
- import java.util.List;
-
- import org.apache.isis.applib.AbstractFactoryAndRepository;
- import org.apache.isis.applib.annotation.ActionSemantics;
- import org.apache.isis.applib.annotation.ActionSemantics.Of;
- import org.apache.isis.applib.annotation.Bookmarkable;
- import org.apache.isis.applib.annotation.MemberOrder;
- import org.apache.isis.applib.annotation.Named;
-
- public class Speakers extends AbstractFactoryAndRepository {
-
- @Override
- public String getId() {
- return "speakers";
- }
-
- public String iconName() {
- return "Speaker";
- }
-
- @Bookmarkable
- @ActionSemantics(Of.SAFE)
- @MemberOrder(sequence = "1")
- public List<Speaker> listAll() {
- return allInstances(Speaker.class);
- }
-
- @MemberOrder(sequence = "2")
- public Speaker create(
- final @Named("Given name") String givenName,
- final @Named("Family name") String familyName) {
- Speaker speaker = getContainer().newTransientInstance(Speaker.class);
- speaker.setGivenName(givenName);
- speaker.setFamilyName(familyName);
- getContainer().persistIfNotAlready(speaker);
- return speaker;
- }
- }
-
-
-#####ConferenceSessionsFixture
-
-* install()
-
- isisJdoSupport.executeUpdate("delete from \"Speaker\"");
-
-* installObjects()
-
- createSpeaker("Dan", "Haywood");
- createSpeaker("Misko", "Hevery");
- createSpeaker("Cyrille", "Martraire");
- createSpeaker("Svetlana", "Haywood");
- createSpeaker("James", "Ward");
- createSpeaker("Jessica", "Kerr");
-
-* createSpeaker() method:
-
- private Speaker createSpeaker(final String givenName, final String familyName) {
- return speakers.create(givenName, familyName);
- }
-
-
-* and inject new domain service:
-
- private Speakers speakers;
- public final void injectSpeakers(final Speakers speakers) {
- this.speakers = speakers;
- }
-
-
-#####isis.properties
-
-* number indicates the ordering in the menu
-
- isis.services = \
- 10:dom.simple.ConferenceSessions,\
- 20:dom.simple.Speakers,\
-
-#### icon
-
-* add icon, `icons/Customer.gif` -> `myapp/dom/src/main/resources/images/Speaker.gif`
-
-
-
-####DEMO
-
-* new domain service
-* list, create
-
-#####Checkpoint:
-`git reset --hard checkpoint-080`
-
-
-##Add reference property
-
-####ConferenceSession
-
-* 'speaker' property (of type 'Speaker')
-
- // //////////////////////////////////////
- // speaker (property)
- // //////////////////////////////////////
-
- private Speaker speaker;
-
- @javax.jdo.annotations.Column(allowsNull="true")
- @MemberOrder(sequence = "3")
- public Speaker getSpeaker() {
- return speaker;
- }
-
- public void setSpeaker(final Speaker speaker) {
- this.speaker = speaker;
- }
-
-####DEMO
-
-* displays field, no way to associate :-(
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-090`
-
-
-##Add autoComplete (and repository queries)
-
-####ConferenceSession
-
-* autoComplete method:
-
- public List<Speaker> autoCompleteSpeaker(String search) {
- return speakers.findByGivenOrFamilyName(search);
- }
-
-* inject service:
-
- private Speakers speakers;
-
- public final void injectSpeakers(final Speakers speakers) {
- this.speakers = speakers;
- }
-
-
-#####Speakers
-
-* finder (hidden because only intended to be called programmatically)
-
- @Hidden
- public List<Speaker> findByGivenOrFamilyName(String search) {
- return getContainer().allMatches(
- new QueryDefault<Speaker>(Speaker.class,
- "findByGivenOrFamilyName",
- "givenOrFamilyName", ".*"+search+".*"));
- }
-
-
-#####Speaker
-
-* Isis configured to use JDO, so add the annotation
-
- @javax.jdo.annotations.Queries({
- @javax.jdo.annotations.Query(
- name="findByGivenOrFamilyName", language="JDOQL",
- value="SELECT "
- + "FROM dom.simple.Speaker "
- + "WHERE givenName.matches(:givenOrFamilyName) "
- + " || familyName.matches(:givenOrFamilyName)")
- })
-
-####DEMO
-
-* can now add speaker
- * using either given or family name
-* Speakers finder is not visible
-
-#####Checkpoint:
-`git reset --hard checkpoint-100`
-
-
-
-
-##Update title (also, prototype actions)
-
-####ConferenceSession
-
-* title imperatively, rather than declaratively
-
- public String title() {
- final TitleBuffer buf = new TitleBuffer();
- if(getSessionTitle().length()>20) {
- buf.append(getSessionTitle().substring(0, 20)).append("...");
- } else {
- buf.append(getSessionTitle());
- }
- if(getSpeaker() != null) {
- buf.append("(").append(container.titleOf(getSpeaker())).append(")");
- }
- return buf.toString();
- }
-
-
-####DEMO:
-
-* metamodel validation exception
-
-
-
-####ConferenceSession
-
-* remove `@Title` from `#sessionTitle`
-
-####ConferenceSessions
-
-* Add prototyping action to domain service
-
- @Bookmarkable
- @Prototype
- @ActionSemantics(Of.SAFE)
- public ConferenceSession firstOne() {
- return listAll().get(0);
- }
-
-
-
-####DEMO:
-* now runs ok
-* prototype action styled differently
-* add speaker to session
-* title changes
-* (optional) run in SERVER mode
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-110`
-
-
-
-
-## Another new entity, Tag
-
-*the motivation for this is that we want to add a new collection of tags from ConferenceSession*
-
-
-####Tag
-
-* new entity (note the `compareTo`, `toString`, using Isis helper methods)
-
- package dom.simple;
-
- import javax.jdo.annotations.IdentityType;
- import javax.jdo.annotations.VersionStrategy;
-
- import org.apache.isis.applib.DomainObjectContainer;
- import org.apache.isis.applib.annotation.Bookmarkable;
- import org.apache.isis.applib.annotation.MemberOrder;
- import org.apache.isis.applib.annotation.ObjectType;
- import org.apache.isis.applib.annotation.Title;
- import org.apache.isis.applib.util.ObjectContracts;
-
- @javax.jdo.annotations.PersistenceCapable(identityType=IdentityType.DATASTORE)
- @javax.jdo.annotations.DatastoreIdentity(
- strategy=javax.jdo.annotations.IdGeneratorStrategy.IDENTITY,
- column="id")
- @javax.jdo.annotations.Version(
- strategy=VersionStrategy.VERSION_NUMBER,
- column="version")
- @ObjectType("TAG")
- @Bookmarkable
- public class Tag implements Comparable<Tag> {
-
-
- // //////////////////////////////////////
- // name (property)
- // //////////////////////////////////////
-
- private String name;
-
- @javax.jdo.annotations.Column(allowsNull = "true")
- @Title
- @MemberOrder(sequence = "1")
- public String getName() {
- return name;
- }
-
- public void setName(final String name) {
- this.name = name;
- }
-
-
-
- // //////////////////////////////////////
- // compareTo
- // //////////////////////////////////////
-
- @Override
- public int compareTo(Tag other) {
- return ObjectContracts.compare(this, other, "name");
- }
-
-
- // //////////////////////////////////////
- // toString
- // //////////////////////////////////////
-
- @Override
- public String toString() {
- return ObjectContracts.toString(this, "name");
- }
-
-
-
- // //////////////////////////////////////
- // Injected
- // //////////////////////////////////////
-
- @SuppressWarnings("unused")
- private DomainObjectContainer container;
- public void injectDomainObjectContainer(final DomainObjectContainer container) {
- this.container = container;
- }
- }
-
-
-####Tags
-
-* new domain service; all actions are hidden
-
- package dom.simple;
-
- import java.util.List;
-
- import org.apache.isis.applib.AbstractFactoryAndRepository;
- import org.apache.isis.applib.annotation.Hidden;
-
- public class Tags extends AbstractFactoryAndRepository {
-
- @Override
- public String getId() {
- return "tags";
- }
- public String iconName() {
- return "Tag";
- }
-
- @Hidden
- public List<Tag> listAll() {
- return allInstances(Tag.class);
- }
- @Hidden
- public Tag create(final String name) {
- final Tag obj = newTransientInstance(Tag.class);
- obj.setName(name);
- persistIfNotAlready(obj);
- return obj;
- }
- }
-
-
-####ConferenceSessionsFixture
-
-* installObjects()
-
- createTag("UX");
- createTag("Mobile");
- createTag("Java");
- createTag("Agile");
- createTag(".NET");
- createTag("Test");
- createTag("Mastery");
- createTag("Web");
- createTag("Architecture");
- createTag("Dev Ops");
- createTag("Cloud");
- createTag("Languages");
- createTag("Tools");
- createTag("Team");
- createTag("Database");
- createTag("Javascript");
- createTag("Keynote");
-
-
-* createTag() method
-
- private Tag createTag(final String name) {
- return tags.create(name);
- }
-
-* inject new service
-
- private Tags tags;
- public final void injectTags(final Tags tags) {
- this.tags = tags;
- }
-
-
-####isis.properties
-
-* no number required, since not visible in UI
-
- isis.services = \
- 10:dom.simple.ConferenceSessions,\
- 20:dom.simple.Speakers,\
- dom.simple.Tags,\
-
-
-#### icon
-
-* add icon, `icons/RedFlag.png` -> `myapp/dom/src/main/resources/images/Tag.png`
-
-####DEMO
-
-* (no visible change, since Tags actions are all hidden)
-
-#####Checkpoint:
-`git reset --hard checkpoint-120`
-
-
-
-
-
-## Add collection
-
-#### ConferenceSession
-
-* tags collection
-
- // //////////////////////////////////////
- // tags (collection)
- // //////////////////////////////////////
-
- @javax.jdo.annotations.Join
- @javax.jdo.annotations.Element(dependent = "false")
- private SortedSet<Tag> tags = new TreeSet<Tag>();
-
- @Render(Render.Type.EAGERLY)
- @Disabled
- @MemberOrder(sequence = "1")
- public SortedSet<Tag> getTags() {
- return tags;
- }
-
- public void setTags(final SortedSet<Tag> tags) {
- this.tags = tags;
- }
-
-
-####DEMO
-* view the DDL
-* open object
-* can't add tags :-(
-
-#####Checkpoint:
-`git reset --hard checkpoint-130`
-
-
-
-##Add actions
-
-####ConferenceSession
-
-* addTag action
-
- // //////////////////////////////////////
- // addTag (action)
- // //////////////////////////////////////
-
- @MemberOrder(sequence = "1")
- public ConferenceSession addTag(final Tag tag) {
- getTags().add(tag);
- return this;
- }
-
-* removeTag action
-
- // //////////////////////////////////////
- // removeTag (action)
- // //////////////////////////////////////
-
- @MemberOrder(sequence = "2")
- public ConferenceSession removeTag(final Tag tag) {
- getTags().remove(tag);
- return this;
- }
-
-
-####ConferenceSessionsFixture
-
-* create(...) method, add three (random) tags for each session
-
- for (Tag tag : random(tags.listAll(), 3)) {
- session.addTag(tag);
- }
-
-* supporting random(...) method
-
- private List<Tag> random(List<Tag> tags, int num) {
- List<Tag> availableTags = Lists.newArrayList(tags);
- List<Tag> selectedTags = Lists.newArrayList();
- while(selectedTags.size()<num) {
- int selected = (int)(availableTags.size() * Math.random());
- try {
- selectedTags.add(availableTags.remove(selected));
- } catch(Exception ex) {}
- }
- return selectedTags;
- }
-
-
-####DEMO:
-* view
-* need to get a reference to the tag again...
-
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-140`
-
-
-
-##Add choices
-
-*an alternative to using autoComplete, useful if the list of choices is relatively short*
-
-####ConferenceSession
-
-* inject Tags repo
-
- private Tags tagRepo;
-
- public final void injectTags(final Tags tags) {
- this.tagRepo = tags;
- }
-
-* added the "choices" supporting method for addTag action (all tags not yet added)
-
- public Collection<Tag> choices0AddTag() {
- List<Tag> tags = Lists.newArrayList(tagRepo.listAll());
- tags.removeAll(getTags());
- return tags;
- }
-
-* added the "choices" supporting method for removeTag action (only those tags previously added)
-
- public Collection<Tag> choices0RemoveTag() {
- return getTags();
- }
-
-####DEMO
-
-* can now add/remove tags, yay!
-
-#####Checkpoint:
-`git reset --hard checkpoint-150`
-
-
-
-##More UI Hints (dynamic, this time)
-
-####DEMO:
-* download layout
-* edit column spans
-* copy `ConferenceSession.layout.json` (below) to `myapp-dom/src/main/java/dom/simple`
-* refresh layout
-
- {
- "columns": [
- {
- "span": 6,
- "memberGroups": {
- "General": {
- "members": {
- "sessionTitle": {},
- "type": {},
- "speaker": {}
- }
- },
- "Scheduling": {
- "members": {
- "date": {}
- }
- }
- }
- },
- {
- "span": 0,
- "memberGroups": {}
- },
- {
- "span": 0,
- "memberGroups": {}
- },
- {
- "span": 6,
- "collections": {
- "tags": {
- "actions": {
- "addTag": {},
- "removeTag": {}
- }
- }
- }
- }
- ],
- "actions": {
- "downloadLayout": {},
- "refreshLayout": {}
- }
- }
-
-#####Checkpoint:
-`git reset --hard checkpoint-160`
-
-
-
-
-##Declarative and imperative business rules
-
-####ConferenceSession
-
-* add RegEx pattern and a maximum length to the 'sessionTitle' property (latter using JDO annotation)
-
- @javax.jdo.annotations.Column(allowsNull="false",length=40)
- @RegEx(validation="[^%]+")
- public String getSessionTitle() {
-
-
-* can't add more than 4 tags
-
- public String disableAddTag(Tag tag) {
- return getTags().size() >= 4? "Cannot add more than 4 tags": null;
- }
-
-####DEMO
-
-* cannot save session whose sessionTitle property has > 40 chars
-* cannot add more than 4 tags to a session
-*
-
-#####Checkpoint:
-`git reset --hard checkpoint-170`
-
-
-
-
-##Other business rules (hiding, validation)
-
-####ConferenceSession
-
-* hideAddTag
-
- public boolean hideAddTag() {
- return getTags().size() >= 4;
- }
-
-####DEMO
-* action becomes hidden
-
-
-####ConferenceSession
-
-* offer tags that are already in the collection, but then prevent using validation
-
- public Collection<Tag> choices0AddTag() {
- List<Tag> tags = Lists.newArrayList(tagRepo.listAll());
- //tags.removeAll(getTags());
- return tags;
- }
-
- public String validateAddTag(Tag tag) {
- return getTags().contains(tag)? "Already added that tag": null;
- }
-
-####DEMO
-* can attempt to add a tag, but prevented
-
-
-####ConferenceSession
-
-* backing out the above changes
-
- public Collection<Tag> choices0AddTag() {
- List<Tag> tags = Lists.newArrayList(tagRepo.listAll());
- tags.removeAll(getTags());
- return tags;
- }
-
- // public String validateAddTag(Tag tag) {
- // return getTags().contains(tag)? "Already added that tag": null;
- // }
-
- // public boolean hideAddTag() {
- // return getTags().size() >= 4;
- // }
-
- public String disableAddTag(Tag tag) {
- return getTags().size() >= 4? "Cannot add more than 4 tags": null;
- }
-
-
-
-##Contributed Actions etc
-
-####ConferenceSessions
-
-* add action, will be contributed to Tag, as both action and a collection
-
- // //////////////////////////////////////
- // Contributions
- // //////////////////////////////////////
-
- // @NotContributed
- @NotInServiceMenu
- public List<ConferenceSession> findByTag(Tag tag) {
- return allMatches(new QueryDefault<ConferenceSession>(
- ConferenceSession.class, "findByTag", "tag", tag));
- }
-
-####ConferenceSession
-
-* declare JDO query
-
- @javax.jdo.annotations.Queries({
- @javax.jdo.annotations.Query(
- name="findByTag", language="JDOQL",
- value="SELECT "
- + "FROM dom.simple.ConferenceSession "
- + "WHERE tags.contains(:tag)")
- })
-
-#### DEMO
-
-* view Tag; shows the sessions that are associated with it
-
-#####Checkpoint:
-`git reset --hard checkpoint-180`
-
-
-
-
-##Bookmarks
-
-####DEMO
-* bookmarked objects
- * eg `ConferenceSession`
-* bookmarked actions
- * eg `ConferenceSessions#listAll`
-
-
-
-
-##CSS
-
-####myapp-webapp/src/main/webapp/css/application.css
-
-* example custom CSS already defined for "x-highlight" and "x-caution" CSS classes
-
-#### ConferenceSession
-
-* addTag, add annotation
-
- @CssClass("x-highlight")
- public ConferenceSession addTag(final Tag tag) {
-
-* removeTag, add annotation
-
- @CssClass("x-caution")
- public ConferenceSession removeTag(final Tag tag) {
-
-
-#### DEMO
-
-* view session object
-
-#####Checkpoint:
-`git reset --hard checkpoint-190`
-
-
-
-##Widgets, also @HomePage
-
-*show Conference sessions on a calendar view. This uses a "third-party" component, up on github*
-
-#### myapp/pom.xml
-
-* parent module, scope=import for dependency management
-
- <dependencyManagement>
- <dependencies>
- ...
-
- <dependency>
- <groupId>com.danhaywood.isis.wicket</groupId>
- <artifactId>danhaywood-isis-wicket-fullcalendar2</artifactId>
- <version>1.3.0</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
-
- </dependencies>
- </dependencyManagement>
-
-
-####myapp-dom/pom.xml
-
-* dom project depends on modules 'applib' (minimal coupling, defines an interface and value type)
-
- <dependencies>
- ...
-
- <dependency>
- <groupId>com.danhaywood.isis.wicket</groupId>
- <artifactId>danhaywood-isis-wicket-fullcalendar2-applib</artifactId>
- </dependency>
-
- </dependencies>
-
-
-####myapp-webapp/pom.xml
-
-* webapp project references the actual widget UI implementation
-
- <dependencies>
- ...
-
- <dependency>
- <groupId>com.danhaywood.isis.wicket</groupId>
- <artifactId>danhaywood-isis-wicket-fullcalendar2-ui</artifactId>
- </dependency>
-
- </dependencies>
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-200`
-
-
-
-
-####ConferenceSession
-
-* declare it implements the `CalendarEventable` interface
-
- public class ConferenceSession implements Comparable<ConferenceSession>
- , com.danhaywood.isis.wicket.fullcalendar2.applib.CalendarEventable {
-
-
-* and implement the required methods:
-
- // //////////////////////////////////////
- // CalendarEventable impl
- // //////////////////////////////////////
-
- @Programmatic
- @Override
- public String getCalendarName() {
- return "date";
- }
-
- @Programmatic
- @Override
- public CalendarEvent toCalendarEvent() {
- return new CalendarEvent(
- getDate().toDateTimeAtStartOfDay(),
- "date",
- container.titleOf(this));
- }
-
-
-##ConferenceSessions
-
-* indicate that the listAll action should be called for the home page
-
- @HomePage
- @Bookmarkable
- @ActionSemantics(Of.SAFE)
- @MemberOrder(sequence = "1")
- public List<ConferenceSession> listAll() {
- return allInstances(ConferenceSession.class);
- }
-
-
-
-####DEMO
-* all sessions displayed automatically on home page
-* switch to calendar view (top right, new icon should appear)
-
-
-#####Checkpoint:
-`git reset --hard checkpoint-210`
-
-
-
-
-##View models
-
-####SpeakerViewModel
-
-* shows summary info of speaker, their sessions, the tags of those sessions
-
- package dom.simple;
-
- import java.util.List;
- import java.util.Set;
-
- import org.apache.isis.applib.DomainObjectContainer;
- import org.apache.isis.applib.ViewModel;
- import org.apache.isis.applib.annotation.MemberOrder;
- import org.apache.isis.applib.annotation.Render;
- import org.apache.isis.applib.annotation.Render.Type;
- import org.apache.isis.applib.annotation.Title;
- import org.apache.isis.applib.services.bookmark.Bookmark;
- import org.apache.isis.applib.services.bookmark.BookmarkService;
-
- import com.google.common.base.Function;
- import com.google.common.base.Predicate;
- import com.google.common.collect.Iterables;
- import com.google.common.collect.Lists;
- import com.google.common.io.BaseEncoding;
-
- public class SpeakerViewModel implements ViewModel {
-
-
- // //////////////////////////////////////
- // ViewModel impl
- // //////////////////////////////////////
-
- @Override
- public String viewModelMemento() {
- Bookmark bookmark = bookmarkService.bookmarkFor(getSpeaker());
- return encode(bookmark);
- }
-
- @Override
- public void viewModelInit(String memento) {
- Bookmark bookmark = decode(memento);
- setSpeaker((Speaker) bookmarkService.lookup(bookmark));
- }
-
- static String encode(Bookmark bookmark) {
- return BaseEncoding.base32().encode(bookmark.toString().getBytes());
- }
-
- private static Bookmark decode(String memento) {
- return new Bookmark(new String(BaseEncoding.base32().decode(memento)));
- }
-
-
- // //////////////////////////////////////
- // speaker (property)
- // //////////////////////////////////////
-
- private Speaker speaker;
-
- @javax.jdo.annotations.Column(allowsNull = "true")
- @Title
- @MemberOrder(sequence = "1")
- public Speaker getSpeaker() {
- return speaker;
- }
-
- public void setSpeaker(final Speaker speaker) {
- this.speaker = speaker;
- }
-
-
-
- // //////////////////////////////////////
- // numberOfSessions (property)
- // //////////////////////////////////////
-
-
- @MemberOrder(sequence = "2")
- public int getNumberOfSessions() {
- return getPresenting().size();
- }
-
-
- // //////////////////////////////////////
- // presenting (collection)
- // //////////////////////////////////////
-
- @Render(Type.EAGERLY)
- @MemberOrder(sequence = "1")
- public List<ConferenceSession> getPresenting() {
- return container.allMatches(ConferenceSession.class, new Predicate<ConferenceSession>() {
- @Override
- public boolean apply(ConferenceSession input) {
- return input.getSpeaker() == getSpeaker();
- }
- });
- }
-
- // //////////////////////////////////////
- // tags (collection)
- // //////////////////////////////////////
-
- @Render(Type.EAGERLY)
- @MemberOrder(sequence = "2")
- public List<Tag> getTags() {
- return Lists.newArrayList(
- Iterables.concat(
- Iterables.transform(
- getPresenting(),
- new Function<ConferenceSession, Set<Tag>>(){
- @Override
- public Set<Tag> apply(ConferenceSession input) {
- return input.getTags();
- }
- })
- ));
- }
-
-
-
- // //////////////////////////////////////
- // Injected
- // //////////////////////////////////////
-
- private DomainObjectContainer container;
- public final void injectDomainObjectContainer(final DomainObjectContainer container) {
- this.container = container;
- }
-
- private BookmarkService bookmarkService;
- public final void injectBookmarkService(final BookmarkService bookmarkService) {
- this.bookmarkService = bookmarkService;
- }
- }
-
-
-####Speakers
-
-* new action to return all speakers as home page (should really be in its own application service...)
-
- // //////////////////////////////////////
- // view models
- // //////////////////////////////////////
-
- @ActionSemantics(Of.SAFE)
- @HomePage
- public List<SpeakerViewModel> all() {
- return Lists.newArrayList(Iterables.transform(listAll(),
- new Function<Speaker, SpeakerViewModel>() {
- @Override
- public SpeakerViewModel apply(Speaker input) {
- Bookmark bookmark = bookmarkService.bookmarkFor(input);
- return getContainer().newViewModelInstance(
- SpeakerViewModel.class,
- SpeakerViewModel.encode(bookmark));
- }
- })
- );
- }
-
-* and inject in the framework-provided `BookmarkService`
-
- // //////////////////////////////////////
- // Injected
- // //////////////////////////////////////
-
- private BookmarkService bookmarkService;
-
- public final void injectBookmarkService(final BookmarkService bookmarkService) {
- this.bookmarkService = bookmarkService;
- }
-
-#### ConferenceSessions
-
-* remove @HomePage annotation from the earlier `listAll()` action
-
-####ConferenceSessionsFixture
-
-* create(...) set up random speaker with each session
-
- session.setSpeaker(randomSpeaker());
-
-* randomSpeaker() supporting method
-
- private Speaker randomSpeaker() {
- List<Speaker> all = speakers.listAll();
- while(true) {
- try {
- int selected = (int)(all.size() * Math.random());
- return all.get(selected);
- } catch(Exception ex){}
- }
- }
-
-
-#### Demo
-* hit the home page
-* select a speaker view model
-
-#####Checkpoint:
-`git reset --hard checkpoint-220`
-
-
-
-## REST API
-
-####DEMO
-
-* [http://localhost:8080/restful](http://localhost:8080/restful)