# HG changeset patch # User Tomas Zeman # Date 1335278519 -7200 # Node ID 2ba4569f2bd69768efe5d9a2cf83c15fc3d75feb # Parent 2b0fafd71c1d40f542e5bd3172b6e47c7bd2377f Location + GPS diff -r 2b0fafd71c1d -r 2ba4569f2bd6 db/db-schema.sql --- a/db/db-schema.sql Fri Apr 20 13:47:21 2012 +0200 +++ b/db/db-schema.sql Tue Apr 24 16:41:59 2012 +0200 @@ -71,6 +71,19 @@ "updated_by" bigint ); create sequence "country_id_seq"; +create table "location" ( + "name" varchar(100) not null, + "updated_at" timestamp not null, + "id" bigint primary key not null, + "latitude" double precision not null, + "longitude" double precision not null, + "note" varchar(10240), + "created_at" timestamp not null, + "created_by" bigint, + "address_id" bigint not null, + "updated_by" bigint + ); +create sequence "location_id_seq"; create table "contact" ( "name" varchar(100) not null, "updated_at" timestamp not null, @@ -149,13 +162,14 @@ -- foreign key constraints : alter table "address" add constraint "addressFK1" foreign key ("city_id") references "city"("id"); alter table "city" add constraint "cityFK2" foreign key ("country_id") references "country"("id"); -alter table "company" add constraint "companyFK3" foreign key ("address_id") references "address"("id"); -alter table "company" add constraint "companyFK4" foreign key ("post_adress_id") references "address"("id") on delete set null; -alter table "bank_account" add constraint "bank_accountFK5" foreign key ("company_id") references "company"("id") on delete cascade; -alter table "company_contact" add constraint "company_contactFK6" foreign key ("entity") references "company"("id") on delete cascade; -alter table "company_contact" add constraint "company_contactFK7" foreign key ("contact") references "contact"("id") on delete cascade; -alter table "user_contact" add constraint "user_contactFK8" foreign key ("entity") references "user"("id") on delete cascade; -alter table "user_contact" add constraint "user_contactFK9" foreign key ("contact") references "contact"("id") on delete cascade; +alter table "location" add constraint "locationFK3" foreign key ("address_id") references "address"("id"); +alter table "company" add constraint "companyFK4" foreign key ("address_id") references "address"("id"); +alter table "company" add constraint "companyFK5" foreign key ("post_adress_id") references "address"("id") on delete set null; +alter table "bank_account" add constraint "bank_accountFK6" foreign key ("company_id") references "company"("id") on delete cascade; +alter table "company_contact" add constraint "company_contactFK7" foreign key ("entity") references "company"("id") on delete cascade; +alter table "company_contact" add constraint "company_contactFK8" foreign key ("contact") references "contact"("id") on delete cascade; +alter table "user_contact" add constraint "user_contactFK9" foreign key ("entity") references "user"("id") on delete cascade; +alter table "user_contact" add constraint "user_contactFK10" foreign key ("contact") references "contact"("id") on delete cascade; -- composite key indexes : alter table "company_contact" add constraint "company_contactCPK" unique("entity","contact"); alter table "user_contact" add constraint "user_contactCPK" unique("entity","contact"); diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/bootstrap/liftweb/Boot.scala --- a/src/main/scala/bootstrap/liftweb/Boot.scala Fri Apr 20 13:47:21 2012 +0200 +++ b/src/main/scala/bootstrap/liftweb/Boot.scala Tue Apr 24 16:41:59 2012 +0200 @@ -20,7 +20,7 @@ import fis.aaa.model._ import fis.aaa.ui._ import fis.crm.ui._ -import fis.geo.ui.{CitySnippet, CountrySnippet} +import fis.geo.ui._ import fis.db.SquerylTxMgr import net.liftweb.common._ import net.liftweb.http._ @@ -55,6 +55,7 @@ UserSnippet.menu, AuthnSnippet.menu, CountrySnippet.menu, + LocationSnippet.menu, CitySnippet.menu) LiftRules.setSiteMap(SiteMap(menus:_*)) diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/model/GeoSchema.scala --- a/src/main/scala/fis/geo/model/GeoSchema.scala Fri Apr 20 13:47:21 2012 +0200 +++ b/src/main/scala/fis/geo/model/GeoSchema.scala Tue Apr 24 16:41:59 2012 +0200 @@ -22,6 +22,7 @@ val cityT = tableWithSeq[City] val addressT = tableWithSeq[Address] val countryT = tableWithSeq[Country] + val locationT = tableWithSeq[Location] val cityAddresses = oneToManyRelation(cityT, addressT). via((c, a) => c.id === a.city) @@ -29,6 +30,9 @@ val countryCities = oneToManyRelation(countryT, cityT). via((country, city) => country.id === city.country) + val locationAddress = oneToManyRelation(addressT, locationT). + via((a, l) => l.address === a.id) + City.cities.default.set { () => from(cityT)( c => select(c) orderBy(c.name asc)) } @@ -38,4 +42,10 @@ object GeoSchema extends GeoSchema +object CityLocations { + def apply(c: City): Iterable[Location] = from(GeoSchema.cityAddresses.left(c), + GeoSchema.locationT)((a, l) => + where(l.address === a.id) select(l) orderBy(l.name asc)) +} + // vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/model/GpsPoint.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/model/GpsPoint.scala Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,68 @@ +/* + * 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.geo.model + +import net.liftweb.common._ +import net.liftweb.http.Templates +import net.liftweb.record.Record +import net.liftweb.record.field._ +import net.liftweb.util._ +import net.liftweb.util.Helpers._ +import net.tz.lift.model._ +import scala.xml.NodeSeq + +trait GpsPoint[T <: Record[T]] { self: T => + val latitude = new DoubleField(this.asInstanceOf[T]) + val longitude = new DoubleField(this.asInstanceOf[T]) + lazy val gps = new GpsPointField(latitude, longitude) +} + +class GpsPointField[T <: Record[T]](lat: DoubleField[T], + lng: DoubleField[T]) extends BaseField { + + type ValueType = String + + protected def load: NodeSeq = + Templates(List("templates-hidden", "gps-field")) openOr NodeSeq.Empty + + private def elem = ( + ".latitude" #> lat.toForm.map(e => addAttributes(e, + "class" -> "latitude")) & + ".longitude" #> lng.toForm.map(e => addAttributes(e, + "class" -> "longitude")) + )(load) + + def toForm = uniqueFieldId match { + case Full(id) => Full(addAttributes(elem, ("id" -> id))) + case _ => Full(elem) + } + + override def asHtml = ( + ".latitude" #> lat.asHtml & + ".longitude" #> lng.asHtml + )(load) + + def get = "Lat: %d, Lng: %d".format(lat.get, lng.get) + def is = get + def validations = Nil + def validate = Nil + def setFilter = Nil + def set(s: String) = get + def name = "gps" + override def displayName = l10n("gps") +} + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/model/Location.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/model/Location.scala Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,37 @@ +/* + * 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.geo.model + +import fis.base.model.Entity +import net.liftweb.record.{MetaRecord, Record} +import net.liftweb.record.field._ +import net.liftweb.squerylrecord.KeyedRecord +import net.tz.lift.model.{FieldLabel => FL} +import org.squeryl.annotations.Column + +class Location private() extends Record[Location] with Entity[Location] with + GpsPoint[Location] { + + def meta = Location + + @Column(name="address_id") + val address = new LongField(this) with AddressField with FL + +} + +object Location extends Location with MetaRecord[Location] + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/model/LocationCrud.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/model/LocationCrud.scala Tue Apr 24 16:41:59 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.geo.model + +import fis.base.model.RecordCrud + +trait LocationCrud extends RecordCrud[Location] { + val table = GeoSchema.locationT +} + +object LocationCrud extends LocationCrud + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/ui/CitySnipppet.scala --- a/src/main/scala/fis/geo/ui/CitySnipppet.scala Fri Apr 20 13:47:21 2012 +0200 +++ b/src/main/scala/fis/geo/ui/CitySnipppet.scala Tue Apr 24 16:41:59 2012 +0200 @@ -40,7 +40,13 @@ private val viewPre = Menu.param[City]("city.view", l10n("City"), parse, encode) / prefix / * >> Title(c => i18n("City %s", c.linkName)) >> - locTpl("entity/view") >> Snippet("panel", panel) >> Hidden + locTpl("city/view") >> Snippet("panel", panel) >> + Snippet("locations", locations) >> Hidden + + private val createLocPre = Menu.param[City]("city.create-loc", + l10n("Create location"), parse, encode) / prefix / * / "create-location" >> + Title(c => i18n("Create location in city %s", c.linkName)) >> + locTpl("entity/form") >> Snippet("form", locationF) >> Hidden private val editPre = Menu.param[City]("city.edit", l10n("Edit"), parse, encode) / prefix / * / EDIT >> @@ -54,7 +60,9 @@ private val listM = listPre >> SecNav(createPre).build private val createM = createPre >> SecNav(listPre).build - private val viewM = viewPre >> (SecNav(editPre) + deletePre).build + private val viewM = viewPre >> (SecNav(editPre) + deletePre + + createLocPre).build + private val createLocM = createLocPre >> SecNav(viewPre).build private val editM = editPre >> SecNav(viewPre).build private val deleteM = deletePre >> SecNav(viewPre).build @@ -62,7 +70,7 @@ private lazy val editLoc = editM.toLoc private lazy val deleteLoc = deleteM.toLoc - val menu = listM submenus(viewM, editM, createM, deleteM) + val menu = listM submenus(viewM, editM, createM, deleteM, createLocM) private def cur = viewLoc.currentValue or editLoc.currentValue or deleteLoc.currentValue @@ -71,6 +79,9 @@ private def panel: CssTr = "*" #> cur.map { c => ViewPanel(fields(c)) } + private def locations: CssTr = "*" #> cur.map { c => + LocationTable(CityLocations(c)) } + object url { def view: City => Box[String] = (viewLoc.calcHref _) andThen (Box !! _) } @@ -119,6 +130,20 @@ } } + private object locationF extends LocationForm { + override def localSetup() { + createLocM.toLoc.currentValue.foreach { c => + address.city(c.id) + } + } + + def onSuccess(l: Location) { + LocationSnippet.url.view(l).foreach { u => + S redirectTo u + } + } + } + } // vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/ui/LocationForm.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/ui/LocationForm.scala Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,51 @@ +/* + * 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.geo.ui + +import fis.base.ui._ +import fis.geo.model._ +import net.liftweb.http._ +import net.liftweb.util._ +import net.tz.lift.model._ + +abstract class LocationForm extends HorizontalScreen with CancelButton with + SaveButton { + + protected object location extends ScreenVar[Location](Location.createRecord) + protected object address extends ScreenVar[Address](Address.createRecord) + + protected def fields(l: Location): List[BaseField] = + (List[FieldContainer](l.name) ++ address.formFields ++ + List(l.gps, l.note)) flatMap(_.allFields) + + override def screenFields = fields(location) + + def onSuccess(l: Location): Unit + + def finish() { + for { + a <- AddressCrud.save(address) + l <- LocationCrud.save(location.address(a.id)) + } { + S notice l10n("Location %s saved.", l.linkName) + onSuccess(l) + } + } + +} + + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/ui/LocationPanel.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/ui/LocationPanel.scala Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,30 @@ +/* + * 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.geo.ui + +import fis.geo.model._ +import fis.base.ui.ViewPanel +import net.liftweb.util.BaseField + +object LocationPanel { + + def fields(l: Location): List[BaseField] = + List(l.name, l.address, l.gps, l.note) + + def apply(l: Location) = ViewPanel(fields(l)) +} + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/ui/LocationSnippet.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/ui/LocationSnippet.scala Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,112 @@ +/* + * 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.geo.ui + +import fis.base.ui._ +import fis.geo.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.model._ +import net.tz.lift.snippet._ +import scala.xml.{Elem, NodeSeq, Text} + +object LocationSnippet extends LocationCrud with EntitySnippet[Location] { + val prefix = "location" + + private val listM = Menu("location.list", l10n("Locations")) / prefix >> + Title(_ => i18n("Locations")) >> + locTpl("entity/list") >> Snippet("list", ClearNodes) >> Hidden + + private val viewPre = Menu.param[Location]("location.view", l10n("Location"), parse, + encode) / prefix / * >> Title(l => i18n("Location %s", l.linkName)) >> + locTpl("entity/view") >> Snippet("panel", panel) >> Hidden + + private val editPre = Menu.param[Location]("location.edit", l10n("Edit"), parse, + encode) / prefix / * / EDIT >> + Title(l => i18n("Edit location %s", l.linkName)) >> + locTpl("entity/form") >> Snippet("form", form) >> Hidden + + private val deletePre = Menu.param[Location]("location.delete", l10n("Delete"), + parse, encode) / prefix / * / DELETE >> + Title(l => i18n("Delete location %s", l.linkName)) >> + locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden + + private val viewM = viewPre >> (SecNav(editPre) + deletePre).build + private val editM = editPre >> SecNav(viewPre).build + private val deleteM = deletePre >> SecNav(viewPre).build + + private lazy val viewLoc = viewM.toLoc + private lazy val editLoc = editM.toLoc + private lazy val deleteLoc = deleteM.toLoc + + val menu = listM submenus(viewM, editM, deleteM) + + private def cur = viewLoc.currentValue or editLoc.currentValue or + deleteLoc.currentValue + + private def panel: CssTr = "*" #> cur.map(LocationPanel(_)) + + object url { + def view: Location => Box[String] = (viewLoc.calcHref _) andThen (Box !! _) + } + + private case class LocationLink(c: Location) extends EntityLink[Location](c, + url.view) + + EntityLink.register[Location](LocationLink(_)) + + private object form extends LocationForm { + + override def localSetup() { + cur.foreach { l => + location(l) + l.address.vend.foreach(address(_)) + } + } + + def onSuccess(l: Location) { + S.redirectTo(viewLoc.calcHref(l)) + } + } + + private object deleteF extends HorizontalScreen with CancelButton with + DeleteButton { + + val confirm = field(l10n("Really delete this location?"), false) + + def finish() { + for { + l <- deleteLoc.currentValue if confirm + a <- l.address.vend + c <- a.city.vend + u <- CitySnippet.url.view(c) + r <- delete(l) + ra <- AddressCrud.delete(a) + n <- r.box(l.linkName) + } { + S notice l10n("Location %s deleted.", n) + S redirectTo u + } + } + } + +} + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/scala/fis/geo/ui/LocationTable.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/geo/ui/LocationTable.scala Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,27 @@ +/* + * 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.geo.ui + +import fis.geo.model._ +import fis.base.ui._ + +object LocationTable extends FieldTable[Location] { + def fields(l: Location) = EntityLink(l) ++ Seq(l.address, l.note) + + def apply(l: Iterable[Location]) = build(Location, l) +} + +// vim: set ts=2 sw=2 et: diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/webapp/city/view.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/city/view.html Tue Apr 24 16:41:59 2012 +0200 @@ -0,0 +1,24 @@ + + + + + City View + + +
+
+
+ +
+
+
+
+

+ +
+
+
+ + + + diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/webapp/css/base.css --- a/src/main/webapp/css/base.css Fri Apr 20 13:47:21 2012 +0200 +++ b/src/main/webapp/css/base.css Tue Apr 24 16:41:59 2012 +0200 @@ -81,3 +81,8 @@ .section { margin-top: 1em; } + +/* GPS */ +input.latitude, input.longitude { + width: 60px; +} diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/webapp/templates-hidden/_resources.html --- a/src/main/webapp/templates-hidden/_resources.html Fri Apr 20 13:47:21 2012 +0200 +++ b/src/main/webapp/templates-hidden/_resources.html Tue Apr 24 16:41:59 2012 +0200 @@ -8,6 +8,7 @@ Edit Add Name + GPS + + Name + Note + Address + + diff -r 2b0fafd71c1d -r 2ba4569f2bd6 src/main/webapp/templates-hidden/_resources_cs.html --- a/src/main/webapp/templates-hidden/_resources_cs.html Fri Apr 20 13:47:21 2012 +0200 +++ b/src/main/webapp/templates-hidden/_resources_cs.html Tue Apr 24 16:41:59 2012 +0200 @@ -8,6 +8,7 @@ Upravit Přidat Název + GPS Odebrat Typ @@ -113,9 +114,10 @@ Město %s smazáno. - Název - Poznámka - Stát + Název + Poznámka + Stát + Lokality @@ -146,6 +148,22 @@ Obec PSČ + + Lokality + Lokalita + Vytvořit lokalitu + Vytvořit lokalitu pro %s + Lokalita %s + Upravit lokalitu %s + Smazat lokalitu %s + Skutečně smazat lokalitu? + Lokalita %s smazána. + Lokalita %s uložena. + + + Název + Poznámka + Adresa