# HG changeset patch # User Tomas Zeman # Date 1333456798 -7200 # Node ID d77d8194ee599c931d76320acbc2cd168f91d9a2 # Parent 041c5c5d2bba729a7c9600cc47db887bd88ebe5f Contact record refactoring diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/bootstrap/liftweb/Boot.scala --- a/src/main/scala/bootstrap/liftweb/Boot.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/main/scala/bootstrap/liftweb/Boot.scala Tue Apr 03 14:39:58 2012 +0200 @@ -16,7 +16,7 @@ package bootstrap.liftweb import fis.base.model._ -import fis.crm.ui.{ContactSnippet, ContactSnippet2} +import fis.crm.ui.ContactSnippet import fis.db.SquerylTxMgr import net.liftweb.common._ import net.liftweb.http._ @@ -37,7 +37,7 @@ import Loc._ val menus = List(Menu("/", "FIS Main page") / "index" >> Hidden, - Menu.i("Home") / "" , ContactSnippet.menu, ContactSnippet2.menu) + Menu.i("Home") / "" , ContactSnippet.menu) LiftRules.setSiteMap(SiteMap(menus:_*)) } diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/fis/base/ui/EntitySnippet.scala --- a/src/main/scala/fis/base/ui/EntitySnippet.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/main/scala/fis/base/ui/EntitySnippet.scala Tue Apr 03 14:39:58 2012 +0200 @@ -15,8 +15,10 @@ */ package fis.base.ui +import fis.base.model.RecordCrud import net.liftweb.common._ import net.liftweb.http._ +import net.liftweb.record._ import net.liftweb.sitemap._ import net.liftweb.util._ import net.liftweb.util.Helpers._ @@ -85,17 +87,27 @@ def deleteMenu = deletePreMenu >> LocTpl(tplDelete) } -trait EntitySnippet[T <: KeyedRecord[Long]] extends SnippetHelpers with - CrudMenu[T] { +trait EntitySnippet[T <: Record[T] with KeyedRecord[Long]] extends + RecordCrud[T] { + + def prefix: String - object cur extends RequestVar[EntityLoc[T]](ListEntity[T]()) + protected object memo extends RequestMemoize[String, Box[T]]() + lazy val parse = { id: String => memo(id, get(id)) } + lazy val encode = { v: T => v.id.toString } + val ADD = "add" + val EDIT = "edit" + val DELETE = "delete" + + /* def listsMenu = Menu(prefix + "s", titleList) / (prefix + "s") >> EarlyResponse(() => Full(RedirectResponse("/" + prefix))) lazy val menu: Menu = listsMenu submenus( listMenu, addMenu, showMenu, editMenu, deleteMenu ) + */ } // vim: set ts=2 sw=2 et: diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/fis/crm/model/Contact.scala --- a/src/main/scala/fis/crm/model/Contact.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/main/scala/fis/crm/model/Contact.scala Tue Apr 03 14:39:58 2012 +0200 @@ -17,73 +17,33 @@ import fis.base.model.{Entity, MetaEntity} import net.liftweb.common._ -import net.liftweb.http.S import net.liftweb.record.{MetaRecord, Record} import net.liftweb.record.field._ -import net.liftweb.util.FieldError -import net.tz.lift.model.OptionalFieldDisplay +import net.liftweb.squerylrecord.RecordTypeMode._ +import net.tz.lift.model.{FieldLabel => FL} +import net.tz.lift.model.{OptionalFieldDisplay => OptDisp} import scala.xml.Text -class OptionalEmailField[OwnerType <: Record[OwnerType]](rec: OwnerType, - maxLength: Int) extends OptionalStringField[OwnerType](rec, maxLength) { - override def validations = validateEmail _ :: Nil - protected def validateEmail(email: ValueType): List[FieldError] = - toBoxMyType(email) match { - case Empty | Full("") => Nil - case Full(v) if EmailField.validEmailAddr_?(v) => Nil - case _ => Text(S.??("invalid.email.address")) - } -} - - class Contact private() extends Record[Contact] with Entity[Contact] { def meta = Contact - val firstName = new StringField(this, 80, "") - val lastName = new StringField(this, 80, "") - val position = new OptionalStringField(this, 40) with OptionalFieldDisplay - val workMail = new EmailField(this, 256) - val privateMail = new OptionalEmailField(this, 256) with OptionalFieldDisplay - val otherMail = new OptionalEmailField(this, 256) with OptionalFieldDisplay - val workMobile = new StringField(this, 40) - val privateMobile = new OptionalStringField(this, 40) with - OptionalFieldDisplay - val otherMobile = new OptionalStringField(this, 40) with OptionalFieldDisplay - val workPhone = new OptionalStringField(this, 40) with OptionalFieldDisplay - val privatePhone = new OptionalStringField(this, 40) with OptionalFieldDisplay - val fax = new OptionalStringField(this, 40) with OptionalFieldDisplay + val firstName = new StringField(this, 80, "") with FL + val lastName = new StringField(this, 80, "") with FL + val position = new OptionalStringField(this, 40) with OptDisp with FL + val workMail = new EmailField(this, 256) with FL + val privateMail = new OptionalEmailField(this, 256) with OptDisp with FL + val otherMail = new OptionalEmailField(this, 256) with OptDisp with FL + val workMobile = new StringField(this, 40) with FL + val privateMobile = new OptionalStringField(this, 40) with OptDisp with FL + val otherMobile = new OptionalStringField(this, 40) with OptDisp with FL + val workPhone = new OptionalStringField(this, 40) with OptDisp with FL + val privatePhone = new OptionalStringField(this, 40) with OptDisp with FL + val fax = new OptionalStringField(this, 40) with OptDisp with FL override def linkName = lastName.get + " " + firstName.get - - def fieldsForView = List(lastName, firstName, position, workMail, workMobile, - workPhone, privateMail, privateMobile, privatePhone, fax, - otherMail, otherMobile, note) } -import net.liftweb.squerylrecord.RecordTypeMode._ - -object Contact extends Contact with MetaRecord[Contact] with - MetaEntity[Contact] { - - def getTable = CrmSchema.contacts - def contacts: List[Contact] = from(getTable) (c => - select(c) orderBy(c.lastName asc, c.firstName asc) - ) toList - - def fieldsForList = List(position, workMail, - workMobile, note) - - def delete(c: Contact) = getTable.delete(c.id) -} - -import org.squeryl.KeyedEntity -import org.squeryl.dsl.CompositeKey3 - -case class EntityContact(val entityId: Long, val contactId: Long, - val typeId: Long) extends KeyedEntity[CompositeKey3[Long, Long, Long]] { - - def id = CompositeKey3(entityId, contactId, typeId) -} +object Contact extends Contact with MetaRecord[Contact] // vim: set ts=2 sw=2 et: diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/fis/crm/model/ContactCrud.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/crm/model/ContactCrud.scala Tue Apr 03 14:39:58 2012 +0200 @@ -0,0 +1,26 @@ +/* + * Copyright 2012 Tomas Zeman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package fis.crm.model + +import fis.base.model.RecordCrud + +trait ContactCrud extends RecordCrud[Contact] { + val table = CrmSchema.contactT +} + +object ContactCrud extends ContactCrud + +// vim: set ts=2 sw=2 et: diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/fis/crm/model/CrmSchema.scala --- a/src/main/scala/fis/crm/model/CrmSchema.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/main/scala/fis/crm/model/CrmSchema.scala Tue Apr 03 14:39:58 2012 +0200 @@ -21,7 +21,11 @@ trait CrmSchema extends BaseSchema with GeoSchema { - val contacts = table[Contact]("contact") + val contactT = tableWithSeq[Contact] + + def allContacts: Iterable[Contact] = from(contactT) (c => + select(c) orderBy(c.lastName asc, c.firstName asc) + ) /* val entityContacts = manyToManyRelation(entities, contacts). @@ -52,4 +56,19 @@ object CrmSchema extends CrmSchema +import org.squeryl.KeyedEntity +import org.squeryl.dsl._ + +case class EntityContact[T](val entityId: Long, val contactId: Long) extends + KeyedEntity[CompositeKey2[Long, Long]] { + + def id = CompositeKey2(entityId, contactId) +} + +case class EntityContactWithType[T](val entityId: Long, val contactId: Long, + val typeId: Long) extends KeyedEntity[CompositeKey3[Long, Long, Long]] { + + def id = CompositeKey3(entityId, contactId, typeId) +} + // vim: set ts=2 sw=2 et: diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/fis/crm/ui/ContactPanel.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/crm/ui/ContactPanel.scala Tue Apr 03 14:39:58 2012 +0200 @@ -0,0 +1,31 @@ +/* + * Copyright 2011-2012 Tomas Zeman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package fis.crm.ui + +import fis.base.ui.ViewPanel +import fis.crm.model._ +import net.liftweb.util.BaseField + +object ContactPanel { + + def fields(c: Contact): List[BaseField] = List(c.lastName, c.firstName, + c.position, c.workMail, c.workMobile, c.workPhone, c.privateMail, + c.privateMobile, c.privatePhone, c.fax, c.otherMail, c.otherMobile, c.note) + + def apply(c: Contact) = ViewPanel(fields(c)) +} + +// vim: set ts=2 sw=2 et: diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/fis/crm/ui/ContactSnippet.scala --- a/src/main/scala/fis/crm/ui/ContactSnippet.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/main/scala/fis/crm/ui/ContactSnippet.scala Tue Apr 03 14:39:58 2012 +0200 @@ -1,5 +1,5 @@ /* - * Copyright 2011 Tomas Zeman + * Copyright 2011-2012 Tomas Zeman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,160 +15,89 @@ */ package fis.crm.ui +import fis.base.ui._ import fis.crm.model._ import net.liftweb.common._ import net.liftweb.http._ import net.liftweb.sitemap._ +import net.liftweb.sitemap.Loc._ import net.liftweb.util._ import net.liftweb.util.Helpers._ -import net.tz.lift.snippet.{A, CssTr, DataTable, LocTpl, TplPanel, SnippetHelpers} +import net.tz.lift.model._ +import net.tz.lift.snippet._ import scala.xml.{Elem, NodeSeq, Text} -sealed trait ContactLoc { - def title: String - def url: List[String] - def tpl: String -} - -case object ListContacts extends ContactLoc { - def title = "Contacts" - def url = Nil - def tpl = "entity/list" -} - -case object AddContact extends ContactLoc { - def title = "Add contact" - def url = List("add") - def tpl = "entity/form" -} - -case class ShowContact(c: Contact) extends ContactLoc { - def title = "Contact " + c.linkName - def url = List(c.id.toString) - def tpl = "entity/view" -} - -case class EditContact(c: Contact) extends ContactLoc { - def title = "Edit contact " + c.linkName - def url = List(c.id.toString, "edit") - def tpl = "entity/form" -} - -case class DeleteContact(c: Contact) extends ContactLoc { - def title = "Delete contact " + c.linkName - def url = List(c.id.toString, "delete") - def tpl = "entity/form" -} - -object AsContactLoc extends Loggable { - def apply(pars: List[String]): Box[ContactLoc] = { - logger.debug("Params: " + pars) - pars match { - case List("add") => Full(AddContact) - case AsLong(id) :: xs => Contact.findByKey(id) map { c => xs match { - case List("edit") => EditContact(c) - case List("delete") => DeleteContact(c) - case _ => ShowContact(c) - }} - case Nil => Full(ListContacts) - case _ => Empty - } - } -} - -object ContactSnippet extends SnippetHelpers { - import Loc._ - +object ContactSnippet extends ContactCrud with EntitySnippet[Contact] { val prefix = "contact" - lazy val menu: Menu = Menu("contact.list", ListContacts.title) / "contacts" >> - EarlyResponse(() => Full(RedirectResponse(url(ListContacts)))) submenus( - Menu("contact.add", AddContact.title) / prefix / "add", - Menu.params[ContactLoc]("contact.edit", "Edit contact", parMenu.parser, - parMenu.encoder) / prefix / * / "edit" >> CalcValue(() => cur.is match { - case ShowContact(c) => Full(ShowContact(c)) - case _ => Empty - }) , - Menu.params[ContactLoc]("contact.delete", "Delete contact", parMenu.parser, - parMenu.encoder) / prefix / * / "delete" >> CalcValue(() => cur.is match { - case ShowContact(c) => Full(ShowContact(c)) - case _ => Empty - }) , - menu1) + val listOp = Menu("contact.list", l10n("Contacts")) / prefix >> + Title(_ => i18n("Contacts")) >> + locTpl("entity/list") >> Snippet("list", list) + + val createOp = Menu("contact.create", l10n("Create")) / prefix / ADD >> + Title(_ => i18n("Create contact")) >> + locTpl("entity/form") >> Snippet("form", form) >> Hidden + + val viewOp = Menu.param[Contact]("contact.view", l10n("Contact"), parse, + encode) / prefix / * >> Title(c => i18n("Contact %s", c.linkName)) >> + locTpl("entity/view") >> Snippet("panel", panel) >> + Hidden + + val editOp = Menu.param[Contact]("contact.edit", l10n("Edit"), parse, + encode) / prefix / * / EDIT >> + Title(c => i18n("Edit contact %s", c.linkName)) >> + locTpl("entity/form") >> Snippet("form", form) >> Hidden + + val deleteOp = Menu.param[Contact]("contact.delete", l10n("Delete"), + parse, encode) / prefix / * / DELETE >> + Title(c => i18n("Delete contact %s", c.linkName)) >> + locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden - lazy val menu1: Menu = parMenu >> new DispatchLocSnippets { - def dispatch = { n => (n, cur.is) match { - case ("list", ListContacts) => listContacts - case ("panel", ShowContact(c)) => ContactPanel(c) - case ("form", EditContact(c)) => ContactForm(c).dispatch("") - case ("form", AddContact) => ContactForm(Contact.createRecord). - dispatch("") - case ("form", DeleteContact(c)) => DeleteContactForm(c).dispatch("") - }} - } >> LocTpl(_.tpl) >> Hidden + lazy val viewLoc: Loc[Contact] = viewOp.toLoc + lazy val editLoc = editOp.toLoc + lazy val deleteLoc = deleteOp.toLoc + + val menu = listOp submenus (viewOp, editOp, createOp, deleteOp) + + def list: CssTr = /*ContactTable(from(CrmSchema.contacts)(c => + select(c) orderBy(c.lastName asc, c.firstName asc)))*/ ClearNodes + + def panel: CssTr = "*" #> viewLoc.currentValue.map { ContactPanel(_) } - lazy val parMenu = Menu.params[ContactLoc]("contact", - Loc.LinkText(l => Text(l.title)), - AsContactLoc(_) pass { _.foreach { cur(_) } }, _.url) / prefix / ** + object form extends LiftScreen { + object c extends ScreenVar[Contact](Contact.createRecord) - def url(l: ContactLoc) = parMenu.toLoc.calcHref(l) - - object cur extends RequestVar[ContactLoc](ListContacts) + override def screenFields: List[BaseField] = ContactPanel.fields(c) - def listContacts: CssTr = { - val cols = Contact.fieldsForList.map { _.displayName } - val cells = Contact.contacts.map { c => - A(url(ShowContact(c)), Text(c.linkName)) :: - (Contact.fieldsForList.map { f => - c.fieldByName(f.name).map { _.asHtml } openOr NodeSeq.Empty - }.toList) }.toList - new DataTable((S ? "Full name") :: cols, cells) + override def localSetup() { + editLoc.currentValue.foreach(c(_)) + } + + def finish() { save(c) foreach { v => + S notice l10n("Contact %s saved", v.linkName) + S.redirectTo(viewLoc.calcHref(v)) + }} } - case class ContactPanel(c: Contact) extends TplPanel(c.fieldsForView.map - { f => (f.displayHtml, f.asHtml) }) + object deleteF extends LiftScreen { + val confirm = field(l10n("Really delete this contact?"), false) + + override def finishButton: Elem = - case class ContactForm(c: Contact) extends LiftScreen { - object contact extends ScreenVar(c) - contact.is.fieldsForView.map { f => addFields(() => f) } - override def finishButton: Elem = - protected def finish() { - val c = contact.is - Contact.getTable.insertOrUpdate(c) - S.notice("Contact " + c.linkName + " saved") - S.redirectTo(url(ShowContact(c))) - () + def finish() { + for { + c <- deleteLoc.currentValue if confirm + r <- delete(c) + n <- r.box(c.linkName) + } { + S notice l10n("Contact %s deleted", n) + S redirectTo listOp.loc.calcDefaultHref + } } } - case class DeleteContactForm(c: Contact) extends LiftScreen { - val confirm = field("Really delete this contact?", false) - override def finishButton: Elem = - protected def finish() { - confirm.is match { - case true => - Contact.delete(c) - S.notice("Contact " + c.linkName + " deleted") - S.redirectTo(url(ListContacts)) - case false => - S.redirectTo(url(ShowContact(c))) - } - () - } - } + } -import fis.base.ui._ - -object ContactSnippet2 extends EntitySnippet[Contact] { - implicit def str2ns(s: String): NodeSeq = Text(s) - def prefix = "contact2" - def asEntity(id: Long): Box[Contact] = Contact.findByKey(id) - def titleList = "Contacts" - def titleAdd = "Create a contact" - def titleShow(c: Contact) = "Contact " + c.linkName - def titleEdit(c: Contact) = "Edit contact " + c.linkName - def titleDelete(c: Contact) = "Delete contact " + c.linkName -} // vim: set ts=2 sw=2 et: diff -r 041c5c5d2bba -r d77d8194ee59 src/main/scala/net/tz/lift/snippet/package.scala --- a/src/main/scala/net/tz/lift/snippet/package.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/main/scala/net/tz/lift/snippet/package.scala Tue Apr 03 14:39:58 2012 +0200 @@ -29,6 +29,7 @@ def a(href: Box[String])(cnt: NodeSeq): NodeSeq = href.dmap(cnt) { l => {cnt} } + def locTpl(p: String): Loc.Template = LocTpl(p) } diff -r 041c5c5d2bba -r d77d8194ee59 src/test/scala/fis/crm/model/ContactSpec.scala --- a/src/test/scala/fis/crm/model/ContactSpec.scala Tue Apr 03 14:31:12 2012 +0200 +++ b/src/test/scala/fis/crm/model/ContactSpec.scala Tue Apr 03 14:39:58 2012 +0200 @@ -22,7 +22,6 @@ import org.scalatest._ class ContactSpec extends AbstractTest with BeforeAndAfterAll { - import CrmSchema._ override def beforeAll() { super.beforeAll @@ -31,13 +30,13 @@ "EntityContact" should "create" in { doInDB { val c1 = Contact.createRecord.name("c1") - contacts.insert(c1) + ContactCrud.save(c1) should be ('defined) /* val t1 = CodeListItem.createRecord.name("t1") codeListItems.insert(t1) */ val c2 = Contact.createRecord.name("c2") // acts here as entity - contacts.insert(c2) + ContactCrud.save(c2) should be ('defined) /* entityContacts.insert(EntityContact(c2.id, c1.id, t1.id))