100 likes | 239 Views
A Grails Project : Share It!. Tetsuya Okuda. Share It! - How it works. A simple asset sharing system Each of the users has his/her own items You can rent others' items. User B: ME. Your Item(s). User A: YOU. Rent : request A to rent an item. Book an item for future rental.
E N D
A Grails Project: Share It! Tetsuya Okuda
Share It! - How it works • A simple asset sharing system • Each of the users has his/her own items • You can rent others' items User B: ME Your Item(s) User A: YOU Rent: request A to rent an item Book an item for future rental Check Out: when A pass the item to B Check In: when B returns the item
Domain Model Booking Information class Booking extends Rental { static constraints = {} } Items Spring Security Classes Item category “Building”, “Book”, ... Current “on-going” rental information - renter: User - dateStart: Date - dateEnd: Date
Domain Model: Cheating GORM class Asset { String name Category category String description User ownedBy Rental rental // on-going rental booleancheckedOut static hasMany = [ queue: Rental → Booking ] String pictures static constraints = { name(blank:false) rental(nullable:true) … class Rental { static belongsTo = [renter: User] Date dateStart Date dateEnd String toString() { dateStart...} static constraints = { } } class Booking extends Rental { static constraints = {} } hasMany simply selects all the Rental instances associated with the Asset instance. I.e. If you assign an instance to rental, it also appears in the queue.
Controller: Partial Scaffolding grails generate-all <domain class> generates everything at once, but I am not really interested in all the controller/view code… Package rental import org.springframework.dao.DataIntegrityViolationException class AssetController { static allowedMethods = [save: "POST", update: "POST", delete: "POST"] def index() { redirect(action: "list", params: params) } def list() { params.max = Math.min(params.max ? params.int('max') : 10, 100) [assetInstanceList: Asset.list(params), assetInstanceTotal: Asset.count()] } def create() { [assetInstance: new Asset(params)] } def save() { def assetInstance = new Asset(params) if (!assetInstance.save(flush: true)) { render(view: "create", model: [assetInstance: assetInstance]) return } flash.message = message(code: 'default.created.message', args: [message(code: 'asset.label', default: 'Asset'), assetInstance.id]) redirect(action: "show", id: assetInstance.id) } def show() { def assetInstance = Asset.get(params.id) if (!assetInstance) { flash.message = message(code: 'default.not.found.message', args: [message(code: 'asset.label', default: 'Asset'), params.id]) redirect(action: "list") return } [assetInstance: assetInstance] } def edit() { : Package rental import org.springframework.dao.DataIntegrityViolationException class AssetController { def scaffold = Asset def list() { params.max = Math.min(params.max ? params.int('max') : 10, 100) [assetInstanceList: Asset.list(params), assetInstanceTotal: Asset.count()] } /* cut all the garbage */ } Cut the garbage, and put def scaffold=<domain>back. Note: you can delete uninteresting *.gsp files as well. {create.gsp, edit.gsp, list.gsp, show.gsp}
View – jQuery UI DatePicker <g:javascript> function bookFormUpdate(e) { $(".jDatePicker").each(function() { var id = $(this).attr("id"); $(this).datepicker({ onClose: function(text, inst) { $("#" + id + "_month").attr("value", new Date(text).getMonth() + 1); $("#" + id + "_day").attr("value", new Date(text).getDate()); $("#" + id + "_year").attr("value", new Date(text).getFullYear()); } }); }); } </g:javascript> <g:form> : <g:if test="${'book' in enabled}"> <g:remoteLink action="bookForm" id="${assetInstance?.id}" update="buttonForm" onSuccess="bookFormUpdate(data)"> <g:message code="rental.button.book.label" default="Book" /> </g:remoteLink> </g:if> : </g:form> <div id="buttonForm"></div> </div> : show.gsp: onClose() copies the DatePicker result value to the hidden fields, to emulate the grails default datepicker. #<id>_month #<id>_day #<id>_year g:remoteLink loads the booking form to #buttonForm, when the “Book” button is clicked. bookFormUpdate() needs to be called to hook up<input class=“jDatePicker”> in the booking form as a jQuerydatepicker. def jDatePicker = { attrs, body -> String name = attrs["name"]; out << "<input type=\"hidden\" id=\"${name+'_day'}\" name=\"${name+'_day'}\" value=\"\"/>" out << "<input type=\"hidden\" id=\"${name+'_month'}\" name=\"${name+'_month'}\" value=\"\"/>" out << "<input type=\"hidden\" id=\"${name+'_year'}\" name=\"${name+'_year'}\" value=\"\"/>" out << "<input type=\"text\" class=\"jDatePicker\" id=\"$name\" name=\"$name\"/>" } Taglib:
View – Dashboard and tabs UrlMappings.groovy: class UrlMappings { static mappings = { "/$controller/$action?/$id?"{ constraints {} } "/"(controller:"asset", action:"dashboard") "500"(view:'/error') } } Map “/” to AssetController.dashboard dashboard.gsp: <html><head> <title><g:message code="default.list.label" args="[entityName]" /></title> </head><body> <g:render template="nav"/> // tabs : <h1>Your Rentals</h1> <g:render template="dlist" model="${[assetInstanceList:yourRentals, assetInstanceTotal:0]}" /> <h1>Your Items</h1> <g:render template="dlist" model="${[assetInstanceList:yourAssets, assetInstanceTotal:0]}" /> </div> </body> _nav.gsp: <div class="nav clear" role="navigation"> <ul> <g:navitem action="dashboard" enabled="${enabled}">Home</g:navitem> <g:navitem action="list" enabled="${enabled}">Item List</g:navitem> <g:navitem action="messages" enabled="${enabled}">Messages</g:navitem> <g:navitem action="create" enabled="${enabled}" disabled="true">New Item</g:navitem> <g:navitem action="show" enabled="${enabled}">Details</g:navitem> <g:navitem action="logout" controller="logout" class="logout" enabled="${enabled}">Logout</g:navitem> </ul> </div> If action == actionName (i.e. the current action), the tab is highlighted
Test – Creating Objects from CSV It is painful for me to write object setup code like this:new <domain>(field1:XXX, field2:”XXXXX”, …..).save() new <domain>(field1:YYY, field2:”YYYYY”, …..).save() // create assets readCSVService.createFromCSV(Asset, "$csv#Assets", ['name','description','pictures'], ['ownedBy':{User.findByUsername(it)}, 'category':{ Category.findByName(it) ?: new Category(name:it).save() }]) Test & BootStrap: def createFromCSV(def T, String path, def select = [], def init = [:], Closure each = { T.getMetaClass().invokeConstructor(it).save(flush:true) }) { readFromCSV(path, select, init, each) } def readFromCSV(String path, def select = [], def init = [:], Closure each = {it}) { def list = [] eachRowsWithKeys(path) { keys, values -> def args = [:]; : ReadCSVService:
Next Steps • Complete refactoring • Scrap the code and build from scratch • With better understanding how grails works • Try more AJAX • More widgets • Uploading picture files with HTML5 • Continue learning software/web