Saturday, September 20, 2014

Getting started with BDD and NUnit testing

 It appears (IMHO, and Googlers') that MS's Test Explorer is shiite.  
 Better runners are: NUnit, TestDriven.NET, ReSharper, etc.  
 (Optional)  
 Writing Behaviour Driven Development tests:  
  - To Install BDDfy (http://docs.teststack.net/bddfy/),   
    run from the NuGet Package Manager Console:   
     Install-Package TestStack.BDDfy  

 Writing Unit Tests:  
  - Create a .UnitTests VisualStudio Project for each Project you wish to test  
  - Use the NUnit syntax (i.e. annotations, see included book or google it) in your tests  
  - To Install NUnit (for the Tests Project, not the SystemUnderTest), run from the NuGet Package Manager Console:   
    Install-Package NUnit  

 Running (and Debugging) Unit Tests in VS:  
  - To install TestDriven.NET (a Test runner; ReSharper is another), run the installer EXE.  
  - Use TestDriven.NET in VisualStudio context menu   
    i.e. right-click in the Solution and/or Project containing tests or right in a test method in the file Editor   
       and select:  
       - 'Run Test(s)' to run i.e. NOT 'Run Tests' [ONLY when in Editor], that uses Microsofts runner...  
       - 'Test With > Debugger' to debug  

Sample code follows:

Largest.cs

 using System;  
 namespace Largest  
 {  
   public class Cmp  
   {  
     public static int Largest(int[] list)  
     {  
       if (list.Length == 0)  
       {  
         throw new ArgumentException("Empty list");  
       }  
       int index, max = list[0];  
       for (index = 0; index < list.Length; index++)  
       {  
         if (list[index] > max)  
         {  
           max = list[index];  
         }  
       }  
       return max;  
     }  
   }  
 }  

LargestTest.cs

 using System;  
 using NUnit.Framework;  
 {  
   [TestFixture]  
   public class LargestTest  
   {  
     [Test]  
     public void LargestOf3()  
     {  
       int[] numbers = new int[] { 9, 8, 7 };  
       Assert.That(Cmp.Largest(numbers), Is.EqualTo(9));  
       numbers = new int[] { 8, 9, 7 };  
       Assert.That(Cmp.Largest(numbers), Is.EqualTo(9));  
       numbers = new int[] { 7, 8, 9 };  
       Assert.That(Cmp.Largest(numbers), Is.EqualTo(9));  
     }  
     [Test]  
     [ExpectedException(typeof(ArgumentException))]  
     public void Empty()  
     {  
       Cmp.Largest(new int[] { });  
     }  
     [Test]  
     public void LargestOf3Alt()  
     {  
       Assert.That(Cmp.Largest(new int[]{ 8, 9, 7 }), Is.EqualTo(9));  
     }  
     [Test]  
     public void Negative()  
     {  
       int[] negatives = new int[] { -9, -8, -7 };  
       Assert.That(Cmp.Largest(negatives), Is.EqualTo(-7));  
     }  
   }  
 }  

BDDDemo.cs

 using System;  
 using Microsoft.VisualStudio.TestTools.UnitTesting;  
 using TestStack.BDDfy;  
 namespace BDDfy.Samples.Atm // Scenarios taken from http://dannorth.net/introducing-bdd/  
 {  
   [TestClass]  
   [Story(  
     AsA = "As an Account Holder",  
     IWant = "I want to withdraw cash from an ATM",  
     SoThat = "So that I can get money when the bank is closed")]  
   public class AccountHolderWithdrawsCash  
   {  
     [TestMethod]  
     public void AccountHasInsufficientFund()  
     {  
       new AccountHasInsufficientFund().BDDfy();  
     }  
     [TestMethod]  
     public void CardHasBeenDisabled()  
     {  
       new CardHasBeenDisabled().BDDfy();  
     }  
     [TestMethod]  
     public void AccountHasSufficientFund()  
     {  
       new AccountHasSufficientFund()  
         .Given(s => s.GivenTheAccountBalanceIs(100), "Given the account balance is $100")  
           .And(s => s.AndTheCardIsValid())  
           .And(s => s.AndTheMachineContainsEnoughMoney())  
         .When(s => s.WhenTheAccountHolderRequests(20), "When the account holder requests $20")  
         .Then(s => s.ThenTheAtmShouldDispense(20), "Then the ATM should dispense $20")  
           .And(s => s.AndTheAccountBalanceShouldBe(80), "And the account balance should be $80")  
           .And(s => s.AndTheCardShouldBeReturned())  
       .BDDfy();  
     }  
   }  
   [TestClass]  
   public class AccountHasSufficientFund  
   {  
     private Card _card;  
     private Atm _atm;  
     public void GivenTheAccountBalanceIs(int balance)  
     {  
       _card = new Card(true, balance);  
     }  
     public void AndTheCardIsValid()  
     {  
     }  
     public void AndTheMachineContainsEnoughMoney()  
     {  
       _atm = new Atm(100);  
     }  
     public void WhenTheAccountHolderRequests(int moneyRequest)  
     {  
       _atm.RequestMoney(_card, moneyRequest);  
     }  
     public void ThenTheAtmShouldDispense(int dispensedMoney)  
     {  
       Assert.AreEqual(dispensedMoney, _atm.DispenseValue);  
     }  
     public void AndTheAccountBalanceShouldBe(int balance)  
     {  
       Assert.AreEqual(balance, _card.AccountBalance);  
     }  
     public void AndTheCardShouldBeReturned()  
     {  
       Assert.IsFalse(_atm.CardIsRetained);  
     }  
   }  
   [TestClass]  
   public class AccountHasInsufficientFund  
   {  
     private Card _card;  
     private Atm _atm;  
     // You can override step text using executable attributes  
     [Given("Given the account balance is $10")]  
     void GivenTheAccountBalanceIs10()  
     {  
       _card = new Card(true, 10);  
     }  
     void AndGivenTheCardIsValid()  
     {  
     }  
     void AndGivenTheMachineContainsEnoughMoney()  
     {  
       _atm = new Atm(100);  
     }  
     [When("When the account holder requests $20")]  
     void WhenTheAccountHolderRequests20()  
     {  
       _atm.RequestMoney(_card, 20);  
     }  
     void ThenTheAtmShouldNotDispenseAnyMoney()  
     {  
       Assert.AreEqual(0, _atm.DispenseValue);  
     }  
     void AndTheAtmShouldSayThereAreInsufficientFunds()  
     {  
       Assert.AreEqual(DisplayMessage.InsufficientFunds, _atm.Message);  
     }  
     void AndTheAccountBalanceShouldBe10()  
     {  
       Assert.AreEqual(10, _card.AccountBalance);  
     }  
     void AndTheCardShouldBeReturned()  
     {  
       Assert.IsFalse(_atm.CardIsRetained);  
     }  
     [TestMethod]  
     public void Execute()  
     {  
       this.BDDfy();  
     }  
   }  
   [TestClass]  
   public class CardHasBeenDisabled  
   {  
     private Card _card;  
     Atm _subject;  
     void GivenTheCardIsDisabled()  
     {  
       _card = new Card(false, 100);  
       _subject = new Atm(100);  
     }  
     void WhenTheAccountHolderRequestsMoney()  
     {  
       _subject.RequestMoney(_card, 20);  
     }  
     void ThenTheAtmShouldRetainTheCard()  
     {  
       Assert.IsTrue(_subject.CardIsRetained);  
     }  
     void AndTheAtmShouldSayTheCardHasBeenRetained()  
     {  
       Assert.AreEqual(DisplayMessage.CardIsRetained, _subject.Message);  
     }  
     [TestMethod]  
     public void Execute()  
     {  
       this.BDDfy();  
     }  
   }  
 }  

BDDDomainObjects

 namespace BDDfy.Samples.Atm  
 {  
   public class Atm  
   {  
     public int ExistingCash { get; private set; }  
     public Atm(int existingCash)  
     {  
       ExistingCash = existingCash;  
     }  
     public void RequestMoney(Card card, int request)  
     {  
       if (!card.Enabled)  
       {  
         CardIsRetained = true;  
         Message = DisplayMessage.CardIsRetained;  
         return;  
       }  
       if (card.AccountBalance < request)  
       {  
         Message = DisplayMessage.InsufficientFunds;  
         return;  
       }  
       DispenseValue = request;  
       card.AccountBalance -= request;  
     }  
     public int DispenseValue { get; set; }  
     public bool CardIsRetained { get; private set; }  
     public DisplayMessage Message { get; private set; }  
   }  
   public class Card  
   {  
     public int AccountBalance { get; set; }  
     private readonly bool _enabled;  
     public Card(bool enabled, int accountBalance)  
     {  
       AccountBalance = accountBalance;  
       _enabled = enabled;  
     }  
     public bool Enabled  
     {  
       get { return _enabled; }  
     }  
   }  
   public enum DisplayMessage  
   {  
     None = 0,  
     CardIsRetained,  
     InsufficientFunds  
   }  
 }  

Sample output

 ------ Test started: Assembly: UnitTests.dll ------  
 Story: Account holder withdraws cash  
      As an Account Holder  
      I want to withdraw cash from an ATM  
      So that I can get money when the bank is closed  
 Scenario: Account has insufficient fund  
      Given the account balance is $10  
       And the card is valid  
       And the machine contains enough money  
      When the account holder requests $20  
      Then the atm should not dispense any money  
       And the atm should say there are insufficient funds  
       And the account balance should be 10  
       And the card should be returned  
 Story: Account holder withdraws cash  
      As an Account Holder  
      I want to withdraw cash from an ATM  
      So that I can get money when the bank is closed  
 Scenario: Card has been disabled  
      Given the card is disabled  
      When the account holder requests money  
      Then the atm should retain the card  
       And the atm should say the card has been retained  
 Story: Account holder withdraws cash  
      As an Account Holder  
      I want to withdraw cash from an ATM  
      So that I can get money when the bank is closed  
 Scenario:   
      Given the account balance is $100  
       And the card is valid  
       And the machine contains enough money  
      When the account holder requests $20  
      Then the ATM should dispense $20  
       And the account balance should be $80  
       And the card should be returned  
 3 passed, 0 failed, 0 skipped, took 0.89 seconds (MSTest 10.0).  

Monday, October 4, 2010

Grails + SmartGWT = ++good

Here is a rehash of this fine posting on hooking up Grails domain objects to SmartGWT widgets via their (ie SmartGWT's) DataSource object (as opposed to GWT's RPC mechanism).

I'm rehashing it here for my own edification; it took me a while to read between the lines wrt/missing import statements & the implicit directory structure being used in the original posting. As I only toy with these technologies occasionally, I tend to forget the details...

I'm working with:
  • WinXP
  • A zip of Grails 1.3.5
  • A zip of GWT 2.0.4
  • A zip of SmartGWT 2.2
Caveat: I needed to disable AVG's Resident Shield to get Grails' grails run-app command to work in my environment. I'll need to recreate that scenario to tell you the exact (obscure) error I was getting.

Prep you environment by either running these commands from a command shell you're going to use or modify your System Control panel's Advanced/Environment Variables/User variables panel:
  • SET GRAILS_HOME=somewhere\grails-1.3.5
  • SET GWT_HOME=somewhere else\gwt-2.0.4
(I'll have to post a little batchfile I found on the net that switches these settings on the fly; e.g. gr8[.cmd] 1.0.4 will flip your current shell to Grails v1.0.4. I use this for Grails & GWT now)

Create a new Grails project via the usual means i.e.
  • Open a command shell
  • cd to your favourite project directory
  • grails create-app projectName
  • {a myriad of files are created}
  • cd projectName
Create a new directory under projectName\lib i.e.
  • cd .\lib
  • md gwt
Put smartgwt.jar into this new directory i.e.
  • copy path_to_smartgwt_zip_exploded\smartgwt.jar .\gwt\
And copy it into the lib directory too:
  • copy .\gwt\smartgwt.jar .
Install the GWT plugin for Grails i.e.
  • cd ..\
  • grails install-plugin gwt
  • {a myriad of files are created}
Create a domain object type e.g. net.myorg.domain.thing
  • grails create-domain-class net.myorg.domain.thing
  • {a myriad of files are created, incl. projectName\grails-app\domain\net\myorg\domain\Thing.groovy}
Edit the domain class

  • package net.myorg.domain

  • class Thing {

  • String name
  • String title
  • String description
  • Boolean isPublic = Boolean.TRUE

  • static constraints = {
  • name(unique: true, blank: false)
  • title(blank: false)
  • description(nullable: true)
  • }
  • }
Create a controller (aka servlet) for the domain object(s) e.g. net.myorg.controller.thing
  • grails create-controller net.myorg.controller.thing
  • {a myriad of files are created, incl. projectName\grails-app\controller\net\myorg\domain\ThingController.groovy}
Edit the controller


  • package net.myorg.controller

  • import groovy.xml.MarkupBuilder
  • import com.smartgwt.client.data.RestDataSource

  • class ThingController {
  • def thingService

  • def list = {
  • log.info "ThingController.list( ${params} )"
  • def things = thingService.findThings();
  • def xml = new MarkupBuilder(response.writer)
  • xml.response() {
  • status(0)
  • data {
  • things.each { theThing ->
  • flushThing xml, theThing
  • }
  • }
  • }
  • }

  • def save = {
  • log.info "ThingController.add( ${params} )"
  • def theThing = thingService.save(params)
  • def xml = new MarkupBuilder(response.writer)
  • xml.response() {
  • status(0)
  • data {
  • flushThing xml, theThing
  • }
  • }
  • }

  • def remove = {
  • log.info "ThingController.remove( ${params} )"
  • thingService.remove(params)
  • def xml = new MarkupBuilder(response.writer)
  • xml.response() {
  • data {
  • status(0)
  • record {
  • id(params.id)
  • }
  • }
  • }
  • }

  • private def flushThing = { xml, thing ->
  • xml.record(
  • id: thing.id,
  • name: thing.name,
  • title: thing.title,
  • description: thing.description,
  • isPublic: thing.isPublic
  • )
  • }
  • }
Create a service for the controller object e.g.net.myorg.service.thing
  • grails create-service net.myorg.service.thing
  • {a myriad of files are created, incl. projectName\grails-app\services\net\myorg\service\ThingService.groovy}
Edit the service

  • package net.myorg.service


  • import net.myorg.domain.Thing

  • class ThingService {

  • static transactional = true

  • Thing findThing(Long id) {
  • Thing.get(id)
  • }

  • def Thing[] findThings() {
  • Thing.list()
  • }

  • def Thing save(Map parameters) {
  • log.info "save( ${parameters} )"
  • def theThing =

  • Thing
  • .get(parameters.id)
  • if (!theThing) {

  • theThing
  • = new Thing()
  • }
  • theThing.properties = parameters;
  • theThing.isPublic =
  • "true".equals(parameters.isPublic) ? true : false
  • theThing.save()
  • }

  • def remove(Map parameters) {
  • log.info "remove( ${parameters} )"
  • Thing.get(parameters.id)?.delete()
  • }
  • }


Compile the Groovy bits
  • grails compile
Create a GWT module
  • grails create-gwt-module net.myorg.app
  • {2 files are produced: [a] projectName\src\gwt\net\myorg\App.gwt.xml and [b] projectName\src\gwt\net\myorg\client\App.java}
Edit the GWT module file (ie App.gwt.xml)
  • Add the line: <inherits name="com.smartgwt.SmartGwt"/>
  • between: <inherits name="com.google.gwt.user.User"/>
  • and: <entry-point class="net.myorg.client.App"/>
Edit the GWT module's Java source file

  • package net.myorg.client;

  • import com.google.gwt.core.client.EntryPoint;
  • import net.myorg.client.ds.ThingDs;

  • import net.myorg.client.view.ThingView;

  • public class app implements EntryPoint {

  • public void onModuleLoad() {
  • ThingDs thingDs = ThingDs.getInstance();
  • ThingView thingView= new ThingView(thingDs);
  • thingView.draw();
  • }
  • }

Create a Java DataSource file ie net.myorg.client.ds.ThingDs.java
  • touch src\gwt\net\myorg\client\ds\ThingDs.java

  • package net.myorg.client.ds;

  • import com.smartgwt.client.data.RestDataSource;
  • import com.smartgwt.client.data.fields.DataSourceIntegerField;
  • import com.smartgwt.client.data.fields.DataSourceTextField;
  • import com.smartgwt.client.widgets.form.fields.TextItem;
  • import com.smartgwt.client.widgets.form.fields.TextAreaItem;
  • import com.smartgwt.client.data.fields.DataSourceBooleanField;
  • import com.smartgwt.client.data.OperationBinding;
  • import com.smartgwt.client.types.DSOperationType;
  • import com.smartgwt.client.types.DSProtocol;

  • public class ThingDs
  • extends RestDataSource
  • {
  • private static ThingDs instance;

  • public static ThingDs getInstance()
  • {
  • if (instance == null)
  • {
  • instance = new ThingDs();
  • }
  • return instance;
  • }

  • private ThingDs()
  • {
  • //set id + general stuff
  • setID("thingDs");
  • setClientOnly(false);

  • //setup fields
  • DataSourceIntegerField idField = new DataSourceIntegerField("id", "ID");
  • idField.setCanEdit(false);
  • idField.setPrimaryKey(true);

  • DataSourceTextField nameField = new DataSourceTextField("name", "Name", 250, true);
  • TextItem nameItem = new TextItem();
  • nameItem.setWidth("100%");
  • nameField.setEditorType(nameItem);

  • DataSourceTextField titleField = new DataSourceTextField("title", "Title", 250, true);
  • TextItem titleItem = new TextItem();
  • titleItem.setWidth("100%");
  • titleField.setEditorType(titleItem);

  • DataSourceTextField descField = new DataSourceTextField("description", "Description", 500, false);
  • TextAreaItem areaItem = new TextAreaItem();
  • areaItem.setLength(500);
  • areaItem.setWidth("100%");
  • descField.setEditorType(areaItem);

  • DataSourceBooleanField isPublicField = new DataSourceBooleanField("isPublic", "Public", 0, false);

  • setFields(idField, nameField, titleField, descField, isPublicField);

  • //setup operations
  • //1. fetch
  • OperationBinding fetch = new OperationBinding(DSOperationType.FETCH, "/projectName/thing/list");
  • fetch.setDataProtocol(DSProtocol.POSTPARAMS);

  • //2. update
  • OperationBinding update = new OperationBinding(DSOperationType.UPDATE, "/projectName/thing/save");
  • update.setDataProtocol(DSProtocol.POSTPARAMS);

  • //3. add
  • OperationBinding add = new OperationBinding(DSOperationType.ADD, "/projectName/thing/save");
  • add.setDataProtocol(DSProtocol.POSTPARAMS);

  • //4. remove
  • OperationBinding remove = new OperationBinding(DSOperationType.REMOVE, "/projectName/thing/remove");
  • remove.setDataProtocol(DSProtocol.POSTPARAMS);
  • setOperationBindings(fetch, update, add, remove);
  • }
  • }

Create a Java View file ie net.myorg.client.view.ThingView.java
  • touch src\gwt\net\myorg\client\view\ThingView.java


  • package net.myorg.client.view;

  • import com.smartgwt.client.widgets.layout.Layout;
  • import com.smartgwt.client.widgets.layout.HLayout;
  • import com.smartgwt.client.widgets.layout.VLayout;
  • import com.smartgwt.client.widgets.events.ClickHandler;
  • import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
  • import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
  • import com.smartgwt.client.widgets.events.ClickEvent;
  • import com.smartgwt.client.widgets.grid.events.RecordDoubleClickHandler;
  • import com.smartgwt.client.widgets.grid.events.RecordDoubleClickEvent;
  • import com.smartgwt.client.widgets.grid.ListGrid;
  • import com.smartgwt.client.widgets.viewer.DetailViewer;
  • import com.smartgwt.client.widgets.Window;
  • import com.smartgwt.client.widgets.form.DynamicForm;
  • import com.smartgwt.client.widgets.IButton;
  • import com.smartgwt.client.widgets.HeaderControl;
  • import com.smartgwt.client.types.HeaderControls;
  • import com.smartgwt.client.types.Alignment;
  • import net.myorg.client.ds.ThingDs;

  • public class ThingView
  • extends HLayout
  • implements ClickHandler
  • {
  • private final ThingDs thingDs;
  • private final ListGrid table = new ListGrid();
  • private final DetailViewer detail = new DetailViewer();
  • private final Window formWindow = new Window();
  • private final DynamicForm form = new DynamicForm();
  • private final IButton addButton = new IButton();
  • private final IButton saveButton = new IButton();
  • private final IButton removeButton = new IButton();
  • private final IButton cancelButton = new IButton();

  • public ThingView(ThingDs thingDs)
  • {
  • this.thingDs = thingDs;
  • initUi();
  • }

  • private void initUi()
  • {
  • //init myself
  • initMySelf();
  • //init listgrid
  • initGrid();
  • //a detail viewer
  • Layout detailsComp = initDetailViewer();
  • //form for editing
  • initEditForm();
  • //button layout
  • addMember(table);
  • addMember(detailsComp);
  • }

  • private void initMySelf()
  • {
  • setWidth100();
  • setHeight100();
  • setMembersMargin(5);
  • setMargin(5);
  • setPadding(10);
  • }

  • private void initGrid()
  • {
  • table.setDataSource(thingDs);
  • table.setDataPageSize(20);
  • table.setAutoFetchData(true);
  • table.setAlign(Alignment.CENTER);
  • table.setWidth("70%");
  • table.setShowAllColumns(false);
  • table.setWrapCells(true);
  • table.setEmptyCellValue("---");
  • table.addRecordClickHandler(new RecordClickHandler()
  • {
  • public void onRecordClick(RecordClickEvent recordClickEvent)
  • {
  • detail.viewSelectedData(table);
  • }
  • });
  • table.addRecordDoubleClickHandler(new RecordDoubleClickHandler()
  • {
  • public void onRecordDoubleClick(RecordDoubleClickEvent recordDoubleClickEvent)
  • {
  • form.editSelectedData(table);
  • formWindow.show();
  • }
  • });
  • }

  • private Layout initDetailViewer()
  • {
  • detail.setGroupTitle("Thing Details");
  • detail.setDataSource(thingDs);
  • detail.setShowEmptyMessage(true);
  • //add button
  • addButton.setTitle("ADD");
  • addButton.setTooltip("Create a new Thing");
  • addButton.addClickHandler(new ClickHandler()
  • {
  • public void onClick(ClickEvent clickEvent)
  • {
  • form.editNewRecord();
  • formWindow.show();
  • }
  • });
  • removeButton.setTitle("REMOVE");
  • removeButton.setTooltip("Delete this Thing");
  • removeButton.addClickHandler(new ClickHandler()
  • {
  • public void onClick(ClickEvent clickEvent)
  • {
  • table.removeSelectedData();
  • table.fetchData();
  • }
  • });
  • HLayout buttonPane = new HLayout();
  • buttonPane.setAlign(Alignment.CENTER);
  • buttonPane.addMember(addButton);
  • buttonPane.addMember(removeButton);
  • VLayout layout = new VLayout(10);
  • layout.addMember(detail);
  • layout.addMember(buttonPane);
  • return layout;
  • }

  • private void initEditForm() {
  • //the form
  • form.setIsGroup(false);
  • form.setDataSource(thingDs);
  • form.setCellPadding(5);
  • form.setWidth("100%");
  • saveButton.setTitle("SAVE");
  • saveButton.setTooltip("Save this Thing instance");
  • saveButton.addClickHandler(new ClickHandler()
  • {
  • public void onClick(ClickEvent clickEvent)
  • {
  • form.saveData();
  • formWindow.hide();
  • }
  • });
  • cancelButton.setTitle("CANCEL");
  • cancelButton.setTooltip("Cancel");
  • cancelButton.addClickHandler(this);
  • HLayout buttons = new HLayout(10);
  • buttons.setAlign(Alignment.CENTER);
  • buttons.addMember(cancelButton);
  • buttons.addMember(saveButton);
  • VLayout dialog = new VLayout(10);
  • dialog.setPadding(10);
  • dialog.addMember(form);
  • dialog.addMember(buttons);
  • //form dialog
  • formWindow.setShowShadow(true);
  • formWindow.setShowTitle(false);
  • formWindow.setIsModal(true);
  • formWindow.setPadding(20);
  • formWindow.setWidth(500);
  • formWindow.setHeight(350);
  • formWindow.setShowMinimizeButton(false);
  • formWindow.setShowCloseButton(true);
  • formWindow.setShowModalMask(true);
  • formWindow.centerInPage();
  • HeaderControl closeControl = new HeaderControl(HeaderControl.CLOSE, this);
  • formWindow.setHeaderControls(HeaderControls.HEADER_LABEL, closeControl);
  • formWindow.addItem(dialog);
  • }

  • public void onClick(ClickEvent clickEvent)
  • {
  • formWindow.hide();
  • }
  • }
Create a (GSP) page to host the module
  • grails create-gwt-page smart.gsp net.myorg.app
Edit the hosting page
  • Add the line: <script>var isomorphicDir = "gwt/net.myorg.App/sc/"(sorry, closing script tag is not showing on blog)
  • just after the TITLE HTML tag
Move the hosting page
  • move web-app\smart.gsp grails-app\views\
Edit the main layout page
  • remove the reference to main.css
Compile the module
  • grails compile-gwt-modules
Run the server
  • grails run-app
View the page
  • Browse to http://localhost:8080/projectName/smart.gsp
  • Start editing Things

Monday, December 14, 2009

IntelliJ's IDEA Java/Groovy/Ruby/PHP/etc. IDE is considered by many to be the best IDE for Java/Groovy, there is a free version.

  • Download and install IntelliJ's IDEA v9.0+ CE from here.

  • Download the latest AribaWeb plugin for IDEA here.

    • md aribaweb\lib in IDEA's plugin directory

    • copy or move the plugin JAR into the above directory

  • Create a new AribaWeb project i.e. type aw and follow the prompts (e.g. see this video)

  • Launch IDEA at the end i.e. MyFirstApp.ipr

    • You'll be prompted to tell IDEA where AribaWeb lives

    • Point it to wherever you installed AiribaWeb

    • Adjust the Ant version to be used for your project after watching this video

    • You can adjust the JDK used by IDEA for your project under File | Project Structure | Project (aka General Settings for Project 'Whatever')

  • BTW, there's a little more info. here, including how to install the Eclipse plugin

Ariba made it big through the last dotcom boom/bust with procurement services via the web for B2B; they (quietly) open-sourced a web development framework (that they've developed continuously since 1999) last summer (AribaWeb) and now it's come to my notice - e.g. they claim 100 times less code than Rails, never mind JSF, Tapestry, Struts2, etc.

Live, in the browser editing of the presentation artifacts is one interesting feature; it's all based on JEE and has an ORM layer over top of Hibernate (something like Grails has i.e. GORM). Read all about it here.

P.S. Somehow Kirk beating the Gorm is still one of my favourite (cheesy) sci-fi scenes; how about you?

Wednesday, November 18, 2009

Location Services

Apple iPods offer a Location Service to programmers for use in applications; imagine being able to get a free yourOrg app from the Apple AppStore that, once logged-in, allowed you to get driving/parking/walking directions to your next class or meeting. The timetabling info. is already available from yourOrg apps.

Location Services uses (SkyHook's) amalgam of GPS (iPhone only), WiFi and cellphone tower triangulation (iPhone only). WiFi has the advantage of being about as accurate as GPS, it works well indoors and it works on both iTouch (~$200) and iPhone (~$500 or $150 +$65/m x 36).

Here is a (manual) webapp that allows you to upload your WiFi's MAC address, latitude & longitude to SyHook's databases.

Just add some programming and we're good to go!

Wednesday, November 4, 2009

Delirious

..my mind goes racing around and around and around it chases it's tail and starts all over again, endlessly. That's what the fever & delerium of this latest flu is like for me.

Now, that its passed, I draw your attention to yet another way we could, as a group of web-application developers, handle authentication & authorization for all those web applications we're sure to build before the next millenium.

This one differs from the one I concocted a few weeks ago, it's based on OpenSSO:

  • A&A is handled completely outside your app, you lockdown URLs by User, Role & Permission
    • It depends upon your using RESTful URLs, you get these out-of-the-box w/Grails
  • A&A is handled by Sun's OpenSSO product
  • Like the name says single-signon for all your web apps
  • You don't even have to write a login page
  • There is, of course, a Grails plugin

Here's some links to a bit of OpenSSO usage:


Fever time!