Location + GPS
authorTomas Zeman <tzeman@volny.cz>
Tue, 24 Apr 2012 16:41:59 +0200
changeset 76 2ba4569f2bd6
parent 75 2b0fafd71c1d
child 77 8f0eddd7aa85
Location + GPS
db/db-schema.sql
src/main/scala/bootstrap/liftweb/Boot.scala
src/main/scala/fis/geo/model/GeoSchema.scala
src/main/scala/fis/geo/model/GpsPoint.scala
src/main/scala/fis/geo/model/Location.scala
src/main/scala/fis/geo/model/LocationCrud.scala
src/main/scala/fis/geo/ui/CitySnipppet.scala
src/main/scala/fis/geo/ui/LocationForm.scala
src/main/scala/fis/geo/ui/LocationPanel.scala
src/main/scala/fis/geo/ui/LocationSnippet.scala
src/main/scala/fis/geo/ui/LocationTable.scala
src/main/webapp/city/view.html
src/main/webapp/css/base.css
src/main/webapp/templates-hidden/_resources.html
src/main/webapp/templates-hidden/_resources_cs.html
src/main/webapp/templates-hidden/gps-field.html
--- 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");
--- 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:_*))
--- 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- /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 <tzeman@volny.cz>
+ *
+ * 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:
--- /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 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>City View</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <div class="row">
+        <div class="span12">
+          <span class="lift:panel"></span>
+        </div>
+      </div> <!-- /row -->
+      <div class="row section">
+        <div class="span12">
+          <h3><span class="lift:loc?locid=city.locations"></span></h3>
+          <span class="lift:locations"></span>
+        </div>
+      </div> <!-- /row -->
+    </div>
+  </body>
+</html>
+
+
--- 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;
+}
--- 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 @@
   <res name="Edit" lang="en" default="true">Edit</res>
   <res name="Add" lang="en" default="true">Add</res>
   <res name="linkName" lang="en" default="true">Name</res>
+  <res name="gps" lang="en" default="true">GPS</res>
   <!--
     Remove
     Entity type
@@ -122,6 +123,7 @@
   <res name="city.name" lang="en" default="true">Name</res>
   <res name="city.note" lang="en" default="true">Note</res>
   <res name="city.country" lang="en" default="true">Country</res>
+  <res name="city.locations" lang="en" default="true">Locations</res>
 
 
   <!-- company
@@ -152,6 +154,26 @@
   <res name="address.city" lang="en" default="true">City</res>
   <res name="address.zipCode" lang="en" default="true">ZIP</res>
 
+
+  <!-- location
+    default strings:
+      Locations
+      Location
+      Create location
+      Create location in city %s
+      Location %s
+      Edit location %s
+      Delete location %s
+      Really delete this location?
+      Location %s deleted.
+      Location %s saved.
+  -->
+  <!-- location fields -->
+  <res name="location.name" lang="en" default="true">Name</res>
+  <res name="location.note" lang="en" default="true">Note</res>
+  <res name="location.address" lang="en" default="true">Address</res>
+
+
 <!--
   vim: et sw=2 ts=2
 -->
--- 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 @@
   <res name="Edit" lang="cs">Upravit</res>
   <res name="Add" lang="cs">Přidat</res>
   <res name="linkName" lang="cs">Název</res>
+  <res name="gps" lang="cs">GPS</res>
   <res name="Remove" lang="cs">Odebrat</res>
   <res name="Entity type" lang="cs">Typ</res>
 
@@ -113,9 +114,10 @@
   <res name="City %s deleted." lang="cs">Město %s smazáno.</res>
 
   <!-- city fields -->
-  <res name="city.name" lang="en" default="true">Název</res>
-  <res name="city.note" lang="en" default="true">Poznámka</res>
-  <res name="city.country" lang="en" default="true">Stát</res>
+  <res name="city.name" lang="cs">Název</res>
+  <res name="city.note" lang="cs">Poznámka</res>
+  <res name="city.country" lang="cs">Stát</res>
+  <res name="city.locations" lang="cs">Lokality</res>
 
 
   <!-- company -->
@@ -146,6 +148,22 @@
   <res name="address.city" lang="cs">Obec</res>
   <res name="address.zipCode" lang="cs">PSČ</res>
 
+  <!-- location -->
+  <res name="Locations" lang="cs">Lokality</res>
+  <res name="Location" lang="cs">Lokalita</res>
+  <res name="Create location" lang="cs">Vytvořit lokalitu</res>
+  <res name="Create location in city %s" lang="cs">Vytvořit lokalitu pro %s</res>
+  <res name="Location %s" lang="cs">Lokalita %s</res>
+  <res name="Edit location %s" lang="cs">Upravit lokalitu %s</res>
+  <res name="Delete location %s" lang="cs">Smazat lokalitu %s</res>
+  <res name="Really delete this location?" lang="cs">Skutečně smazat lokalitu?</res>
+  <res name="Location %s deleted." lang="cs">Lokalita %s smazána.</res>
+  <res name="Location %s saved." lang="cs">Lokalita %s uložena.</res>
+
+  <!-- location fields -->
+  <res name="location.name" lang="cs">Název</res>
+  <res name="location.note" lang="cs">Poznámka</res>
+  <res name="location.address" lang="cs">Adresa</res>
 
 
 <!--
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/templates-hidden/gps-field.html	Tue Apr 24 16:41:59 2012 +0200
@@ -0,0 +1,4 @@
+<span>
+  Latitude: <span class="latitude"></span>
+  Longitude: <span class="longitude"></span>
+</span>