├── .gitignore ├── README ├── project ├── build.properties └── build │ └── LiftProject.scala └── src ├── main ├── resources │ ├── .keep │ └── props │ │ └── default.props ├── scala │ ├── bootstrap │ │ └── liftweb │ │ │ └── Boot.scala │ └── code │ │ ├── comet │ │ └── .keep │ │ ├── db │ │ └── TodoDB.scala │ │ ├── lib │ │ └── MongoProtoUser.scala │ │ ├── model │ │ ├── ToDo.scala │ │ └── User.scala │ │ ├── snippet │ │ ├── TD.scala │ │ └── Util.scala │ │ └── view │ │ └── .keep └── webapp │ ├── WEB-INF │ └── web.xml │ ├── images │ └── ajax-loader.gif │ ├── index.html │ ├── static │ └── index.html │ └── templates-hidden │ ├── default.html │ └── wizard-all.html └── test ├── resources └── .keep └── scala ├── LiftConsole.scala ├── RunWebApp.scala └── code └── AppTest.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # use glob syntax. 2 | syntax: glob 3 | *.ser 4 | *.class 5 | *~ 6 | *.bak 7 | *.off 8 | *.old 9 | .DS_Store 10 | 11 | # logs 12 | derby.log 13 | 14 | # eclipse conf file 15 | .settings 16 | .classpath 17 | .project 18 | .manager 19 | 20 | # building 21 | target 22 | build 23 | null 24 | tmp* 25 | dist 26 | test-output 27 | 28 | # sbt 29 | target 30 | lib_managed 31 | src_managed 32 | project/boot 33 | 34 | # db 35 | lift_proto* 36 | 37 | # other scm 38 | .svn 39 | .CVS 40 | .hg* 41 | 42 | # switch to regexp syntax. 43 | # syntax: regexp 44 | # ^\.pc/ 45 | 46 | # IntelliJ 47 | *.iml 48 | *.ipr 49 | *.iws 50 | .idea 51 | 52 | # Pax Runner (for easy OSGi launching) 53 | runner 54 | 55 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Install Java, Simple Build Tool, and Git 2 | 3 | Clone this repository with: 4 | 5 | git clone git://github.com/ghostm/lift-todo-mongodb.git 6 | 7 | Then cd into lift-todo-mongodb and type: 8 | sbt 9 | 10 | At the sbt prompt, type: 11 | update 12 | 13 | Then: 14 | jetty-run 15 | 16 | Make sure MongoDB is running 17 | 18 | Point your browser to: 19 | http://localhost:8080/ 20 | 21 | Voila: Lift's ToDo app using MongoDB 22 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | #Project properties 2 | #Fri Apr 23 11:24:20 PDT 2010 3 | project.organization=Lift 4 | project.name=Lift ToDo MongoDB 5 | sbt.version=0.7.4 6 | project.version=0.1 7 | def.scala.version=2.7.7 8 | build.scala.versions=2.8.1 9 | project.initialize=false 10 | -------------------------------------------------------------------------------- /project/build/LiftProject.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | class LiftProject(info: ProjectInfo) extends DefaultWebProject(info) { 4 | val liftVersion = "2.3-RC3" 5 | 6 | // uncomment the following if you want to use the snapshot repo 7 | //val scalatoolsSnapshot = ScalaToolsSnapshots 8 | 9 | // If you're using JRebel for Lift development, uncomment 10 | // this line 11 | // override def scanDirectories = Nil 12 | 13 | override def libraryDependencies = Set( 14 | "net.liftweb" %% "lift-webkit" % liftVersion % "compile->default", 15 | "net.liftweb" %% "lift-mongodb" % liftVersion % "compile->default", 16 | "net.liftweb" %% "lift-mongodb-record" % liftVersion % "compile->default", 17 | "org.mortbay.jetty" % "jetty" % "6.1.22" % "test->default", 18 | "junit" % "junit" % "4.5" % "test->default", 19 | "org.scala-tools.testing" %% "specs" % "1.6.7" % "test->default", 20 | "ch.qos.logback" % "logback-classic" % "0.9.26" 21 | ) ++ super.libraryDependencies 22 | } -------------------------------------------------------------------------------- /src/main/resources/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/resources/.keep -------------------------------------------------------------------------------- /src/main/resources/props/default.props: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/resources/props/default.props -------------------------------------------------------------------------------- /src/main/scala/bootstrap/liftweb/Boot.scala: -------------------------------------------------------------------------------- 1 | package bootstrap.liftweb 2 | 3 | import _root_.net.liftweb.util._ 4 | import _root_.net.liftweb.common._ 5 | import _root_.net.liftweb.http._ 6 | import _root_.net.liftweb.http.provider._ 7 | import _root_.net.liftweb.sitemap._ 8 | import _root_.net.liftweb.sitemap.Loc._ 9 | import Helpers._ 10 | import _root_.code.model._ 11 | import _root_.code.db._ 12 | 13 | 14 | /** 15 | * A class that's instantiated early and run. It allows the application 16 | * to modify lift's environment 17 | */ 18 | class Boot { 19 | def boot { 20 | // where to search snippet 21 | LiftRules.addToPackages("code") 22 | // Build SiteMap 23 | val entries = Menu(Loc("Home", List("index"), "Home")) :: 24 | Menu(Loc("Static", Link(List("static"), true, "/static/index"), 25 | "Static Content")) :: 26 | User.sitemap 27 | LiftRules.setSiteMap(SiteMap(entries:_*)) 28 | 29 | /* 30 | * Show the spinny image when an Ajax call starts 31 | */ 32 | LiftRules.ajaxStart = Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd) 33 | 34 | /* 35 | * Make the spinny image go away when it ends 36 | */ 37 | LiftRules.ajaxEnd = 38 | Full(() => LiftRules.jsArtifacts.hide("ajax-loader").cmd) 39 | 40 | LiftRules.early.append(makeUtf8) 41 | 42 | LiftRules.loggedInTest = Full(() => User.loggedIn_?) 43 | 44 | //Set up MongoDB 45 | TodoDB.setup 46 | } 47 | 48 | /** 49 | * Force the request to be UTF-8 50 | */ 51 | private def makeUtf8(req: HTTPRequest) { 52 | req.setCharacterEncoding("UTF-8") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/code/comet/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/scala/code/comet/.keep -------------------------------------------------------------------------------- /src/main/scala/code/db/TodoDB.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 WorldWide Conferencing, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package code { 15 | package db { 16 | 17 | import _root_.net.liftweb.mongodb._ 18 | import _root_.net.liftweb.util._ 19 | import _root_.net.liftweb.common._ 20 | import net.liftweb.json.DefaultFormats 21 | import net.liftweb.json.JsonAST._ 22 | import net.liftweb.json.JsonParser._ 23 | import net.liftweb.json.JsonDSL._ 24 | 25 | import com.mongodb.{BasicDBObject, BasicDBObjectBuilder, DBObject} 26 | 27 | object TodoDB { 28 | def setup { 29 | MongoDB.defineDb(DefaultMongoIdentifier, MongoAddress(MongoHost(), "test_todo")) 30 | } 31 | 32 | def isMongoRunning: Boolean = { 33 | try { 34 | MongoDB.use(DefaultMongoIdentifier) ( db => { db.getLastError } ) 35 | true 36 | } 37 | catch { 38 | case e => false 39 | } 40 | } 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/code/lib/MongoProtoUser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2010-2012 Matthew Henderson 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package code.lib 17 | 18 | import _root_.scala.xml.{NodeSeq, Node, Text, Elem} 19 | import net.liftweb.mongodb.record.field.MongoPasswordField 20 | import net.liftweb.record.Field 21 | import net.liftweb.common._ 22 | import _root_.net.liftweb.http.{S, js, SHtml} 23 | import js._ 24 | import JsCmds._ 25 | import _root_.scala.xml.{NodeSeq, Node, Text, Elem} 26 | import _root_.scala.xml.transform._ 27 | import _root_.net.liftweb.sitemap._ 28 | import _root_.net.liftweb.sitemap.Loc._ 29 | import _root_.net.liftweb.util.Helpers._ 30 | import _root_.net.liftweb.util._ 31 | import _root_.net.liftweb.common._ 32 | import _root_.net.liftweb.util.Mailer._ 33 | import S._ 34 | import _root_.net.liftweb.proto.{ProtoUser => GenProtoUser} 35 | import net.liftweb.mongodb.record.{MongoRecord, MongoId, MongoMetaRecord} 36 | import net.liftweb.mongodb._ 37 | import _root_.net.liftweb.json.DefaultFormats 38 | import _root_.net.liftweb.json.JsonDSL._ 39 | import _root_.net.liftweb.json.JsonAST.JObject 40 | 41 | import net.liftweb.record.field._ 42 | trait UserIdAsString { 43 | def userIdAsString: String 44 | } 45 | /** 46 | * ProtoUser is a base class that gives you a "User" that has a first name, 47 | * last name, email, etc. 48 | */ 49 | trait ProtoUser[T <: ProtoUser[T]] extends MongoRecord[T] with UserIdAsString with MongoId[T]{ 50 | self: T => 51 | 52 | /** 53 | * The primary key field for the User. You can override the behavior 54 | * of this field: 55 | *
 56 |    * override lazy val id = new MyMappedLongClass(this) {
 57 |    *   println("I am doing something different")
 58 |    * }
 59 |    * 
60 | */ 61 | 62 | //protected class MyMappedLongClass(obj: T) extends LongField(obj) 63 | 64 | /** 65 | * Convert the id to a String 66 | */ 67 | def userIdAsString: String = _id.is.toString 68 | 69 | /** 70 | * The first name field for the User. You can override the behavior 71 | * of this field: 72 | *
 73 |    * override lazy val firstName = new MyFirstName(this, 32) {
 74 |    *   println("I am doing something different")
 75 |    * }
 76 |    * 
77 | */ 78 | 79 | lazy val firstName: StringField[T] = new MyFirstName(this, 32) 80 | 81 | protected class MyFirstName(obj: T, size: Int) extends StringField(obj, size) { 82 | override def displayName = owner.firstNameDisplayName 83 | override val fieldId = Some(Text("txtFirstName")) 84 | } 85 | 86 | /** 87 | * The string name for the first name field 88 | */ 89 | def firstNameDisplayName = ??("first.name") 90 | 91 | /** 92 | * The last field for the User. You can override the behavior 93 | * of this field: 94 | *
 95 |    * override lazy val lastName = new MyLastName(this, 32) {
 96 |    *   println("I am doing something different")
 97 |    * }
 98 |    * 
99 | */ 100 | lazy val lastName: StringField[T] = new MyLastName(this, 32) 101 | 102 | protected class MyLastName(obj: T, size: Int) extends StringField(obj, size) { 103 | override def displayName = owner.lastNameDisplayName 104 | override val fieldId = Some(Text("txtLastName")) 105 | } 106 | 107 | /** 108 | * The last name string 109 | */ 110 | def lastNameDisplayName = ??("last.name") 111 | 112 | /** 113 | * The email field for the User. You can override the behavior 114 | * of this field: 115 | *
116 |    * override lazy val email = new MyEmail(this, 48) {
117 |    *   println("I am doing something different")
118 |    * }
119 |    * 
120 | */ 121 | lazy val email: EmailField[T] = new MyEmail(this, 48) 122 | 123 | protected class MyEmail(obj: T, size: Int) extends EmailField(obj, size) { 124 | private def valUnique(emailValue: ValueType): List[FieldError] = 125 | toBoxMyType(emailValue) match { 126 | case Full(email) => { 127 | owner.meta.findAll("email", email) match { 128 | case Nil => Nil 129 | case usr :: Nil if (usr.id == owner.id) => Empty 130 | case _ => Text(S.??("unique.email.address")) 131 | } 132 | } 133 | case _ => Text(S.??("unique.email.address")) 134 | } 135 | 136 | override def displayName = owner.emailDisplayName 137 | override def validations = valUnique _ :: super.validations 138 | override val fieldId = Some(Text("txtEmail")) 139 | } 140 | 141 | /** 142 | * The email first name 143 | */ 144 | def emailDisplayName = ??("email.address") 145 | 146 | /** 147 | * The password field for the User. You can override the behavior 148 | * of this field: 149 | *
150 |    * override lazy val password = new MyPassword(this) {
151 |    *   println("I am doing something different")
152 |    * }
153 |    * 
154 | */ 155 | lazy val password: MongoPasswordField[T] = new MyPassword(this) 156 | 157 | protected class MyPassword(obj: T) extends MongoPasswordField(obj) { 158 | override def displayName = owner.passwordDisplayName 159 | } 160 | 161 | /** 162 | * The display name for the password field 163 | */ 164 | def passwordDisplayName = ??("password") 165 | 166 | /** 167 | * The superuser field for the User. You can override the behavior 168 | * of this field: 169 | *
170 |    * override lazy val superUser = new MySuperUser(this) {
171 |    *   println("I am doing something different")
172 |    * }
173 |    * 
174 | */ 175 | lazy val superUser: BooleanField[T] = new MySuperUser(this) 176 | 177 | protected class MySuperUser(obj: T) extends BooleanField(obj) { 178 | override def defaultValue = false 179 | } 180 | 181 | def niceName: String = (firstName.is, lastName.is, email.is) match { 182 | case (f, l, e) if f.length > 1 && l.length > 1 => f+" "+l+" ("+e+")" 183 | case (f, _, e) if f.length > 1 => f+" ("+e+")" 184 | case (_, l, e) if l.length > 1 => l+" ("+e+")" 185 | case (_, _, e) => e 186 | } 187 | 188 | def shortName: String = (firstName.is, lastName.is) match { 189 | case (f, l) if f.length > 1 && l.length > 1 => f+" "+l 190 | case (f, _) if f.length > 1 => f 191 | case (_, l) if l.length > 1 => l 192 | case _ => email.is 193 | } 194 | 195 | def niceNameWEmailLink = {niceName} 196 | } 197 | 198 | trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends MongoMetaRecord[ModelType] with MongoId[ModelType] with GenProtoUser { 199 | self: ModelType => 200 | 201 | type TheUserType = ModelType 202 | //ensureIndex(("email" -> 1), true) // unique email 203 | /** 204 | * What's a field pointer for the underlying CRUDify 205 | */ 206 | type FieldPointerType = Field[_, TheUserType] 207 | 208 | /** 209 | * Based on a FieldPointer, build a FieldPointerBridge 210 | */ 211 | protected implicit def buildFieldBridge(from: FieldPointerType): FieldPointerBridge = new MyPointer(from) 212 | 213 | 214 | protected class MyPointer(from: FieldPointerType) extends FieldPointerBridge { 215 | /** 216 | * What is the display name of this field? 217 | */ 218 | def displayHtml: NodeSeq = from.displayHtml 219 | 220 | /** 221 | * Does this represent a pointer to a Password field 222 | */ 223 | def isPasswordField_? : Boolean = from match { 224 | case a: MongoPasswordField[_] => true 225 | case _ => false 226 | } 227 | } 228 | 229 | /** 230 | * Convert an instance of TheUserType to the Bridge trait 231 | */ 232 | protected implicit def typeToBridge(in: TheUserType): UserBridge = 233 | new MyUserBridge(in) 234 | 235 | /** 236 | * Bridges from TheUserType to methods used in this class 237 | */ 238 | protected class MyUserBridge(in: TheUserType) extends UserBridge { 239 | /** 240 | * Convert the user's primary key to a String 241 | */ 242 | def userIdAsString: String = in.id.toString 243 | 244 | /** 245 | * Return the user's first name 246 | */ 247 | def getFirstName: String = in.firstName.is 248 | 249 | /** 250 | * Return the user's last name 251 | */ 252 | def getLastName: String = in.lastName.is 253 | 254 | /** 255 | * Get the user's email 256 | */ 257 | def getEmail: String = in.email.is 258 | 259 | /** 260 | * Is the user a superuser 261 | */ 262 | def superUser_? : Boolean = in.superUser.is 263 | 264 | /** 265 | * Has the user been validated? 266 | */ 267 | def validated_? : Boolean = in.validated.is 268 | 269 | /** 270 | * Does the supplied password match the actual password? 271 | */ 272 | def testPassword(toTest: Box[String]): Boolean = 273 | toTest.map(in.password.isMatch) openOr false 274 | 275 | /** 276 | * Set the validation flag on the user and return the user 277 | */ 278 | def setValidated(validation: Boolean): TheUserType = 279 | in.validated(validation) 280 | 281 | /** 282 | * Set the unique ID for this user to a new value 283 | */ 284 | //TODO 285 | def resetUniqueId(): TheUserType = { 286 | in 287 | } 288 | 289 | /** 290 | * Return the unique ID for the user 291 | */ 292 | def getUniqueId(): String = in._id.toString 293 | 294 | /** 295 | * Validate the user 296 | */ 297 | def validate: List[FieldError] = in.validate 298 | 299 | /** 300 | * Given a list of string, set the password 301 | */ 302 | def setPasswordFromListString(pwd: List[String]): TheUserType = { 303 | pwd match { 304 | case x1 :: x2 :: Nil if x1 == x2 => in.password.setPassword(x1) 305 | case _ => Nil 306 | } 307 | in 308 | } 309 | 310 | /** 311 | * Save the user to backing store 312 | */ 313 | def save(): Boolean = { 314 | in.save(true) 315 | true 316 | } 317 | } 318 | 319 | /** 320 | * Given a field pointer and an instance, get the field on that instance 321 | */ 322 | protected def computeFieldFromPointer(instance: TheUserType, pointer: FieldPointerType): Box[BaseField] = { 323 | println(instance.fieldByName(pointer.name)) 324 | instance.fieldByName(pointer.name) 325 | } 326 | 327 | 328 | /** 329 | * Given an username (probably email address), find the user 330 | */ 331 | protected def findUserByEmail(email: String): Box[TheUserType] = { 332 | var searchListHeadOption = meta.findAll(("email" -> email)).headOption 333 | searchListHeadOption match { 334 | case Some(x) => Full(x) 335 | case None => return Empty 336 | } 337 | } 338 | 339 | protected def findUserByUserName(email: String): Box[TheUserType] = findUserByEmail(email) 340 | 341 | /** 342 | * Given a unique id, find the user 343 | */ 344 | protected def findUserByUniqueId(id: String): Box[TheUserType] = { 345 | var searchListHeadOption = meta.findAll(("_id" -> id)).headOption 346 | searchListHeadOption match { 347 | case Some(x) => Full(x) 348 | case None => return Empty 349 | } 350 | } 351 | 352 | /** 353 | * Create a new instance of the User 354 | */ 355 | protected def createNewUserInstance(): TheUserType = createRecord 356 | 357 | /** 358 | * Given a String representing the User ID, find the user 359 | */ 360 | protected def userFromStringId(id: String): Box[TheUserType] = find(id) 361 | 362 | /** 363 | * The list of fields presented to the user at sign-up 364 | */ 365 | def signupFields: List[FieldPointerType] = List(firstName, 366 | lastName, 367 | email, 368 | locale, 369 | timezone, 370 | password) 371 | 372 | /** 373 | * The list of fields presented to the user for editing 374 | */ 375 | def editFields: List[FieldPointerType] = List(firstName, 376 | lastName, 377 | email, 378 | locale, 379 | timezone) 380 | 381 | } 382 | /** 383 | * ProtoUser is bare bones. MetaProtoUser contains a bunch 384 | * more fields including a validated flag, locale, timezone, etc. 385 | */ 386 | trait MegaProtoUser[T <: MegaProtoUser[T]] extends ProtoUser[T]{ 387 | self: T => 388 | 389 | /** 390 | * The has the user been validated. 391 | * You can override the behavior 392 | * of this field: 393 | *
394 |    * override lazy val validated = new MyValidated(this, 32) {
395 |    *   println("I am doing something different")
396 |    * }
397 |    * 
398 | */ 399 | lazy val validated: BooleanField[T] = new MyValidated(this) 400 | 401 | protected class MyValidated(obj: T) extends BooleanField(obj) { 402 | override def defaultValue = false 403 | override val fieldId = Some(Text("txtValidated")) 404 | } 405 | 406 | /** 407 | * The locale field for the User. 408 | * You can override the behavior 409 | * of this field: 410 | *
411 |    * override lazy val locale = new MyLocale(this, 32) {
412 |    *   println("I am doing something different")
413 |    * }
414 |    * 
415 | */ 416 | lazy val locale = new MyLocale(this) 417 | 418 | protected class MyLocale(obj: T) extends LocaleField(obj) { 419 | override def displayName = owner.localeDisplayName 420 | override val fieldId = Some(Text("txtLocale")) 421 | } 422 | 423 | /** 424 | * The time zone field for the User. 425 | * You can override the behavior 426 | * of this field: 427 | *
428 |    * override lazy val timezone = new MyTimeZone(this, 32) {
429 |    *   println("I am doing something different")
430 |    * }
431 |    * 
432 | */ 433 | lazy val timezone = new MyTimeZone(this) 434 | 435 | protected class MyTimeZone(obj: T) extends TimeZoneField(obj) { 436 | override def displayName = owner.timezoneDisplayName 437 | override val fieldId = Some(Text("txtTimeZone")) 438 | } 439 | 440 | /** 441 | * The string for the timezone field 442 | */ 443 | def timezoneDisplayName = ??("time.zone") 444 | 445 | /** 446 | * The string for the locale field 447 | */ 448 | def localeDisplayName = ??("locale") 449 | 450 | } -------------------------------------------------------------------------------- /src/main/scala/code/model/ToDo.scala: -------------------------------------------------------------------------------- 1 | package code.model 2 | import scala.List 3 | import net.liftweb.util.FieldError 4 | import net.liftweb.record.field._ 5 | import net.liftweb.common._ 6 | import net.liftweb.json.JsonAST._ 7 | import com.mongodb._ 8 | import com.mongodb.util.JSON 9 | import org.bson.types.ObjectId 10 | import net.liftweb.mongodb.record._ 11 | import net.liftweb.mongodb.record.field._ 12 | import net.liftweb.mongodb._ 13 | import net.liftweb.json.JsonDSL._ 14 | 15 | class ToDo private () extends MongoRecord[ToDo] with MongoId[ToDo]{ 16 | def meta = ToDo 17 | 18 | object ownerdocId extends ObjectIdField(this) { 19 | def obj = User.find(value) 20 | } 21 | object done extends BooleanField(this) 22 | object priority extends IntField(this) { 23 | override def defaultValue = 5 24 | override def validations = validPriority _ :: super.validations 25 | 26 | def validPriority(in: Int): List[FieldError] = 27 | if (in > 0 && in <= 10) Nil 28 | else List(FieldError(this, Priority must be 1-10)) 29 | } 30 | object desc extends StringField(this, 128) { 31 | override def validations = 32 | validLength _ :: 33 | super.validations 34 | def validLength(in: String): List[FieldError] = { 35 | if (in.size >= 3) Nil 36 | else List(FieldError(this, Description must be at least 3 characters)) 37 | } 38 | 39 | } 40 | } 41 | 42 | object ToDo extends ToDo with MongoMetaRecord[ToDo]{ 43 | lazy val priorityList = (1 to 10).map(v => (v.toString, v.toString)) 44 | 45 | def findAll(tempOwnerBox:Box[User], excludeDone:Boolean):List[ToDo] = { 46 | val tempUser = tempOwnerBox.open_! 47 | val tempReturn = ToDo.findAll("ownerdocId" -> JString(tempUser.userIdAsString)) 48 | if(excludeDone) 49 | return tempReturn.filter(_.done.value == false) 50 | tempReturn 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/scala/code/model/User.scala: -------------------------------------------------------------------------------- 1 | package code { 2 | package model { 3 | 4 | import _root_.net.liftweb.record.field._ 5 | import _root_.net.liftweb.common._ 6 | 7 | import code.db._ 8 | import code.lib._ 9 | 10 | /** 11 | * The singleton that has methods for accessing the database 12 | */ 13 | object User extends User with MetaMegaProtoUser[User] { 14 | override def screenWrap = Full( 15 | ) 16 | 17 | // comment this line out to require email validations 18 | override def skipEmailValidation = true 19 | } 20 | 21 | /** 22 | * An O-R mapped "User" class that includes first name, last name, password and we add a "Personal Essay" to it 23 | */ 24 | class User extends MegaProtoUser[User] { 25 | def meta = User // what's the "meta" server 26 | 27 | // define an additional field for a personal essay 28 | object textArea extends StringField(this, 2048) { 29 | override def displayName = "Personal Essay" 30 | } 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/code/snippet/TD.scala: -------------------------------------------------------------------------------- 1 | package code.snippet 2 | 3 | import code._ 4 | import net.liftweb.common.Full 5 | import model._ 6 | 7 | import net.liftweb._ 8 | import http._ 9 | 10 | import SHtml._ 11 | import S._ 12 | 13 | import js._ 14 | import JsCmds._ 15 | 16 | import mapper._ 17 | 18 | import util._ 19 | import Helpers._ 20 | 21 | import scala.xml.{NodeSeq, Text} 22 | 23 | object QueryNotDone extends SessionVar(false) 24 | 25 | class TD { 26 | def add(form: NodeSeq) = { 27 | val tempUser = User.currentUser.open_! 28 | var todo = ToDo.createRecord 29 | var newDesc = todo.desc.toString 30 | var newPriorityString = todo.priority.toString 31 | 32 | def checkAndSave(): Unit = { 33 | todo.desc.set(newDesc) 34 | todo.priority.set(newPriorityString.toInt) 35 | todo.ownerdocId.set(tempUser.id) 36 | println(todo+" "+todo.priority.value+" "+todo.desc.value) 37 | todo.validate match { 38 | case Nil => todo.save ; S.notice("Added "+todo.desc) 39 | case xs => S.error(xs) ; S.mapSnippet("TD.add", doBind) 40 | } 41 | } 42 | 43 | def doBind(form: NodeSeq) = 44 | bind("todo", form, 45 | "priority" -> SHtml.text(newPriorityString, newPriorityString = _), 46 | "desc" -> SHtml.text(newDesc, newDesc = _), 47 | "submit" -> submit("New", checkAndSave)) 48 | 49 | doBind(form) 50 | } 51 | 52 | def list(html: NodeSeq) = { 53 | val id = S.attr("all_id").open_! 54 | def inner(): NodeSeq = { 55 | def reDraw() = SetHtml(id, inner()) 56 | 57 | bind("todo", html, 58 | "exclude" -> 59 | ajaxCheckbox(QueryNotDone, v => {QueryNotDone(v); reDraw}), 60 | "list" -> doList(reDraw) _) 61 | } 62 | 63 | inner() 64 | } 65 | 66 | /* 67 | private def toShow = 68 | ToDo.findAll(By(ToDo.owner, User.currentUser), 69 | if (QueryNotDone) By(ToDo.done, false) 70 | else Ignore[ToDo], 71 | OrderBy(ToDo.done, Ascending), 72 | OrderBy(ToDo.priority, Descending), 73 | OrderBy(ToDo.desc, Ascending)) 74 | */ 75 | //private def toShow = ToDo.findAll() 76 | 77 | private def desc(td: ToDo, reDraw: () => JsCmd) = 78 | swappable({td.desc.toString}, 79 | {ajaxText(td.desc.toString, 80 | v => {td.desc.set(v); td.save; reDraw()})} 81 | ) 82 | 83 | private def doList(reDraw: () => JsCmd)(html: NodeSeq): NodeSeq = 84 | ToDo.findAll(User.currentUser, QueryNotDone). 85 | flatMap(td => 86 | bind("todo", html, 87 | "check" -> ajaxCheckbox(td.done.value, 88 | v => {td.done.set(v); td.save; reDraw()}), 89 | "priority" -> 90 | ajaxSelect(ToDo.priorityList, Full(td.priority.toString), 91 | v => {td.priority.set(v.toInt); td.save; reDraw()}), 92 | "desc" -> desc(td, reDraw) 93 | )) 94 | } 95 | -------------------------------------------------------------------------------- /src/main/scala/code/snippet/Util.scala: -------------------------------------------------------------------------------- 1 | package code.snippet 2 | 3 | import scala.xml.{NodeSeq} 4 | import code._ 5 | import model._ 6 | 7 | class Util { 8 | def in(html: NodeSeq) = 9 | if (User.loggedIn_?) html else NodeSeq.Empty 10 | 11 | def out(html: NodeSeq) = 12 | if (!User.loggedIn_?) html else NodeSeq.Empty 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/code/view/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/scala/code/view/.keep -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | LiftFilter 10 | Lift Filter 11 | The Filter that intercepts lift calls 12 | net.liftweb.http.LiftFilter 13 | 14 | 15 | 16 | 17 | LiftFilter 18 | /* 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/webapp/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostm/lift-todo-mongodb/a89ff978dd169b176d0b2c0e4dfd385f55c1066f/src/main/webapp/images/ajax-loader.gif -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | Please 3 | Log In 4 | 5 | 6 | 7 |
8 |
Exclude done
9 |
    10 | 11 |
  • 12 | 13 | 14 | 15 | 16 | To Do 17 |
  • 18 |
    19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 40 | 41 | 42 | 43 | 48 | 49 |
Description:To Do
33 | Priority 34 | 36 | 37 | 38 | 39 |
  44 | 45 | 46 | 47 |
50 |
51 |
52 |
-------------------------------------------------------------------------------- /src/main/webapp/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | Static content... everything you put in the /static 3 | directory will be served without additions to SiteMap 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/templates-hidden/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | code:app:0.1-SNAPSHOT 8 | 9 | 10 |