From 1b8ae4a35b3c0cfb9a2903046592484f2328b4cb Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Tue, 15 Jul 2014 20:30:31 -0700 Subject: [PATCH 01/68] first commit --- .gitignore | 20 + LICENSE | 8 + README | 7 + activator.properties | 4 + app/controllers/Application.scala | 112 ++++ app/models/Models.scala | 188 +++++++ app/views/createForm.scala.html | 36 ++ app/views/editForm.scala.html | 39 ++ app/views/list.scala.html | 113 ++++ app/views/main.scala.html | 36 ++ app/views/twitterBootstrapInput.scala.html | 12 + build.sbt | 15 + conf/application.conf | 38 ++ conf/evolutions/default/1.sql | 43 ++ conf/evolutions/default/2.sql | 626 +++++++++++++++++++++ conf/messages | 3 + conf/routes | 24 + project/build.properties | 1 + project/plugins.sbt | 20 + public/stylesheets/bootstrap.min.css | 330 +++++++++++ public/stylesheets/main.css | 65 +++ test/ApplicationSpec.scala | 87 +++ test/IntegrationSpec.scala | 66 +++ test/ModelSpec.scala | 58 ++ tutorial/index.html | 14 + 25 files changed, 1965 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README create mode 100644 activator.properties create mode 100644 app/controllers/Application.scala create mode 100644 app/models/Models.scala create mode 100644 app/views/createForm.scala.html create mode 100644 app/views/editForm.scala.html create mode 100644 app/views/list.scala.html create mode 100644 app/views/main.scala.html create mode 100644 app/views/twitterBootstrapInput.scala.html create mode 100644 build.sbt create mode 100644 conf/application.conf create mode 100644 conf/evolutions/default/1.sql create mode 100644 conf/evolutions/default/2.sql create mode 100644 conf/messages create mode 100644 conf/routes create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 public/stylesheets/bootstrap.min.css create mode 100644 public/stylesheets/main.css create mode 100644 test/ApplicationSpec.scala create mode 100644 test/IntegrationSpec.scala create mode 100644 test/ModelSpec.scala create mode 100644 tutorial/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0cf5555af --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +logs +project/project +project/target +target +tmp +.history +dist +/.idea +/*.iml +/out +/.idea_modules +.classpath +.project +/RUNNING_PID +.settings +.target +.cache +bin +.DS_Store +activator-sbt-*-shim.sbt diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..4baedcb95 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +This software is licensed under the Apache 2 license, quoted below. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project 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. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 000000000..066d62a38 --- /dev/null +++ b/README @@ -0,0 +1,7 @@ +This is a classic CRUD application, backed by a JDBC database. It demonstrates: + +- Accessing a JDBC database, using Anorm. +- Achieving, table pagination and CRUD forms. +- Integrating with a CSS framework (Twitter Bootstrap ). + +Twitter Bootstrap requires a different form layout to the default one that the Play form helper generates, so this application also provides an example of integrating a custom form input constructor. \ No newline at end of file diff --git a/activator.properties b/activator.properties new file mode 100644 index 000000000..ec1f1c319 --- /dev/null +++ b/activator.properties @@ -0,0 +1,4 @@ +name=computer-database +title=Computer Database +description=This is a classic CRUD application, backed by a JDBC database. It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). +tags=scala,play,anorm,pagination \ No newline at end of file diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala new file mode 100644 index 000000000..d325c122f --- /dev/null +++ b/app/controllers/Application.scala @@ -0,0 +1,112 @@ +package controllers + +import play.api._ +import play.api.mvc._ +import play.api.data._ +import play.api.data.Forms._ + +import anorm._ + +import views._ +import models._ + +/** + * Manage a database of computers + */ +object Application extends Controller { + + /** + * This result directly redirect to the application home. + */ + val Home = Redirect(routes.Application.list(0, 2, "")) + + /** + * Describe the computer form (used in both edit and create screens). + */ + val computerForm = Form( + mapping( + "id" -> ignored(None:Option[Long]), + "name" -> nonEmptyText, + "introduced" -> optional(date("yyyy-MM-dd")), + "discontinued" -> optional(date("yyyy-MM-dd")), + "company" -> optional(longNumber) + )(Computer.apply)(Computer.unapply) + ) + + // -- Actions + + /** + * Handle default path requests, redirect to computers list + */ + def index = Action { Home } + + /** + * Display the paginated list of computers. + * + * @param page Current page number (starts from 0) + * @param orderBy Column to be sorted + * @param filter Filter applied on computer names + */ + def list(page: Int, orderBy: Int, filter: String) = Action { implicit request => + Ok(html.list( + Computer.list(page = page, orderBy = orderBy, filter = ("%"+filter+"%")), + orderBy, filter + )) + } + + /** + * Display the 'edit form' of a existing Computer. + * + * @param id Id of the computer to edit + */ + def edit(id: Long) = Action { + Computer.findById(id).map { computer => + Ok(html.editForm(id, computerForm.fill(computer), Company.options)) + }.getOrElse(NotFound) + } + + /** + * Handle the 'edit form' submission + * + * @param id Id of the computer to edit + */ + def update(id: Long) = Action { implicit request => + computerForm.bindFromRequest.fold( + formWithErrors => BadRequest(html.editForm(id, formWithErrors, Company.options)), + computer => { + Computer.update(id, computer) + Home.flashing("success" -> "Computer %s has been updated".format(computer.name)) + } + ) + } + + /** + * Display the 'new computer form'. + */ + def create = Action { + Ok(html.createForm(computerForm, Company.options)) + } + + /** + * Handle the 'new computer form' submission. + */ + def save = Action { implicit request => + computerForm.bindFromRequest.fold( + formWithErrors => BadRequest(html.createForm(formWithErrors, Company.options)), + computer => { + Computer.insert(computer) + Home.flashing("success" -> "Computer %s has been created".format(computer.name)) + } + ) + } + + /** + * Handle computer deletion. + */ + def delete(id: Long) = Action { + Computer.delete(id) + Home.flashing("success" -> "Computer has been deleted") + } + +} + diff --git a/app/models/Models.scala b/app/models/Models.scala new file mode 100644 index 000000000..84c2ffd02 --- /dev/null +++ b/app/models/Models.scala @@ -0,0 +1,188 @@ +package models + +import java.util.{Date} + +import play.api.db._ +import play.api.Play.current + +import anorm._ +import anorm.SqlParser._ + +import scala.language.postfixOps + +case class Company(id: Option[Long] = None, name: String) +case class Computer(id: Option[Long] = None, name: String, introduced: Option[Date], discontinued: Option[Date], companyId: Option[Long]) + + +/** + * Helper for pagination. + */ +case class Page[A](items: Seq[A], page: Int, offset: Long, total: Long) { + lazy val prev = Option(page - 1).filter(_ >= 0) + lazy val next = Option(page + 1).filter(_ => (offset + items.size) < total) +} + +object Computer { + + // -- Parsers + + /** + * Parse a Computer from a ResultSet + */ + val simple = { + get[Option[Long]]("computer.id") ~ + get[String]("computer.name") ~ + get[Option[Date]]("computer.introduced") ~ + get[Option[Date]]("computer.discontinued") ~ + get[Option[Long]]("computer.company_id") map { + case id~name~introduced~discontinued~companyId => Computer(id, name, introduced, discontinued, companyId) + } + } + + /** + * Parse a (Computer,Company) from a ResultSet + */ + val withCompany = Computer.simple ~ (Company.simple ?) map { + case computer~company => (computer,company) + } + + // -- Queries + + /** + * Retrieve a computer from the id. + */ + def findById(id: Long): Option[Computer] = { + DB.withConnection { implicit connection => + SQL("select * from computer where id = {id}").on('id -> id).as(Computer.simple.singleOpt) + } + } + + /** + * Return a page of (Computer,Company). + * + * @param page Page to display + * @param pageSize Number of computers per page + * @param orderBy Computer property used for sorting + * @param filter Filter applied on the name column + */ + def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Page[(Computer, Option[Company])] = { + + val offest = pageSize * page + + DB.withConnection { implicit connection => + + val computers = SQL( + """ + select * from computer + left join company on computer.company_id = company.id + where computer.name like {filter} + order by {orderBy} nulls last + limit {pageSize} offset {offset} + """ + ).on( + 'pageSize -> pageSize, + 'offset -> offest, + 'filter -> filter, + 'orderBy -> orderBy + ).as(Computer.withCompany *) + + val totalRows = SQL( + """ + select count(*) from computer + left join company on computer.company_id = company.id + where computer.name like {filter} + """ + ).on( + 'filter -> filter + ).as(scalar[Long].single) + + Page(computers, page, offest, totalRows) + + } + + } + + /** + * Update a computer. + * + * @param id The computer id + * @param computer The computer values. + */ + def update(id: Long, computer: Computer) = { + DB.withConnection { implicit connection => + SQL( + """ + update computer + set name = {name}, introduced = {introduced}, discontinued = {discontinued}, company_id = {company_id} + where id = {id} + """ + ).on( + 'id -> id, + 'name -> computer.name, + 'introduced -> computer.introduced, + 'discontinued -> computer.discontinued, + 'company_id -> computer.companyId + ).executeUpdate() + } + } + + /** + * Insert a new computer. + * + * @param computer The computer values. + */ + def insert(computer: Computer) = { + DB.withConnection { implicit connection => + SQL( + """ + insert into computer values ( + (select next value for computer_seq), + {name}, {introduced}, {discontinued}, {company_id} + ) + """ + ).on( + 'name -> computer.name, + 'introduced -> computer.introduced, + 'discontinued -> computer.discontinued, + 'company_id -> computer.companyId + ).executeUpdate() + } + } + + /** + * Delete a computer. + * + * @param id Id of the computer to delete. + */ + def delete(id: Long) = { + DB.withConnection { implicit connection => + SQL("delete from computer where id = {id}").on('id -> id).executeUpdate() + } + } + +} + +object Company { + + /** + * Parse a Company from a ResultSet + */ + val simple = { + get[Option[Long]]("company.id") ~ + get[String]("company.name") map { + case id~name => Company(id, name) + } + } + + /** + * Construct the Map[String,String] needed to fill a select options set. + */ + def options: Seq[(String,String)] = DB.withConnection { implicit connection => + SQL("select * from company order by name").as(Company.simple *). + foldLeft[Seq[(String, String)]](Nil) { (cs, c) => + c.id.fold(cs) { id => cs :+ (id.toString -> c.name) } + } + } + +} + diff --git a/app/views/createForm.scala.html b/app/views/createForm.scala.html new file mode 100644 index 000000000..6142f29d0 --- /dev/null +++ b/app/views/createForm.scala.html @@ -0,0 +1,36 @@ +@(computerForm: Form[Computer], companies: Seq[(String, String)]) + +@import helper._ + +@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.f) } + +@main { + +

Add a computer

+ + @form(routes.Application.save()) { + +
+ + @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") + @inputDate(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @inputDate(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + + @select( + computerForm("company"), + companies, + '_label -> "Company", '_default -> "-- Choose a company --", + '_showConstraints -> false + ) + + +
+ +
+ or + Cancel +
+ + } + +} diff --git a/app/views/editForm.scala.html b/app/views/editForm.scala.html new file mode 100644 index 000000000..591aaf171 --- /dev/null +++ b/app/views/editForm.scala.html @@ -0,0 +1,39 @@ +@(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)]) + +@import helper._ + +@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.f) } + +@main { + +

Edit computer

+ + @form(routes.Application.update(id)) { + +
+ + @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") + @inputDate(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @inputDate(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + + @select( + computerForm("company"), + companies, + '_label -> "Company", '_default -> "-- Choose a company --", + '_showConstraints -> false + ) + +
+ +
+ or + Cancel +
+ + } + + @form(routes.Application.delete(id), 'class -> "topRight") { + + } + +} diff --git a/app/views/list.scala.html b/app/views/list.scala.html new file mode 100644 index 000000000..b94506915 --- /dev/null +++ b/app/views/list.scala.html @@ -0,0 +1,113 @@ +@(currentPage: Page[(Computer, Option[Company])], currentOrderBy: Int, currentFilter: String)(implicit flash: play.api.mvc.Flash) + +@**************************************** +* Helper generating navigation links * +****************************************@ +@link(newPage: Int, newOrderBy: Option[Int] = None) = @{ + routes.Application.list(newPage, newOrderBy.map { orderBy => + if(orderBy == scala.math.abs(currentOrderBy)) -currentOrderBy else orderBy + }.getOrElse(currentOrderBy), currentFilter) + +} + +@********************************** +* Helper generating table headers * +***********************************@ +@header(orderBy: Int, title: String) = { + + @title + +} + +@main { + +

@Messages("computers.list.title", currentPage.total)

+ + @flash.get("success").map { message => +
+ Done! @message +
+ } + +
+ + @helper.form(action=routes.Application.list()) { + + + } + + Add a new computer + +
+ + @Option(currentPage.items).filterNot(_.isEmpty).map { computers => + + + + + @header(2, "Computer name") + @header(3, "Introduced") + @header(4, "Discontinued") + @header(5, "Company") + + + + + @computers.map { + case (computer, company) => { + + + + + + + } + } + + +
@computer.name + @computer.introduced.map(_.format("dd MMM yyyy")).getOrElse { - } + + @computer.discontinued.map(_.format("dd MMM yyyy")).getOrElse { - } + + @company.map(_.name).getOrElse { - } +
+ + + + }.getOrElse { + +
+ Nothing to display +
+ + } + + +} + + \ No newline at end of file diff --git a/app/views/main.scala.html b/app/views/main.scala.html new file mode 100644 index 000000000..d06e6d528 --- /dev/null +++ b/app/views/main.scala.html @@ -0,0 +1,36 @@ +@(content: Html) + + + + + Computers database + + @************************************* + + + + + + *************************************@ + + + + + + + + +
+

+ + Play sample application — Computer database + +

+
+ +
+ @content +
+ + + diff --git a/app/views/twitterBootstrapInput.scala.html b/app/views/twitterBootstrapInput.scala.html new file mode 100644 index 000000000..4c27a78d8 --- /dev/null +++ b/app/views/twitterBootstrapInput.scala.html @@ -0,0 +1,12 @@ +@(elements: helper.FieldElements) + +@************************************************** +* Generate input according twitter bootsrap rules * +**************************************************@ +
+ +
+ @elements.input + @elements.infos.mkString(", ") +
+
diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..f78286914 --- /dev/null +++ b/build.sbt @@ -0,0 +1,15 @@ +name := "computer-database-scala" + +version := "0.0.1-SNAPSHOT" + +scalaVersion := "2.11.1" + +libraryDependencies ++= Seq( + jdbc, + anorm, + "org.webjars" % "jquery" % "2.1.1", + "org.webjars" % "bootstrap" % "3.1.1", + "org.webjars" % "font-awesome" % "4.1.0" +) + +lazy val root = (project in file(".")).enablePlugins(PlayScala) \ No newline at end of file diff --git a/conf/application.conf b/conf/application.conf new file mode 100644 index 000000000..8a5ccdb7f --- /dev/null +++ b/conf/application.conf @@ -0,0 +1,38 @@ +# Configuration + +application.name=computer-database + +# Secret key +# ~~~~~ +# The secret key is used to secure cryptographics functions. +# If you deploy your application to several instances be sure to use the same key! +application.secret="E27D^[_9W" + +# Database configuration +# ~~~~~ +# You can declare as many datasources as you want. +# By convention, the default datasource is named `default` +db.default.driver=org.h2.Driver +db.default.url="jdbc:h2:mem:play" + +# Assets configuration +# ~~~~~ +"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" + +# Logger +# ~~~~~ +# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory . + +# Root logger: +logger.root=ERROR + +# Logger used by the framework: +logger.play=INFO + +# Logger provided to your application: +logger.application=DEBUG + + + + + diff --git a/conf/evolutions/default/1.sql b/conf/evolutions/default/1.sql new file mode 100644 index 000000000..317d2eadb --- /dev/null +++ b/conf/evolutions/default/1.sql @@ -0,0 +1,43 @@ +# --- First database schema + +# --- !Ups + +set ignorecase true; + +create table company ( + id bigint not null, + name varchar(255) not null, + constraint pk_company primary key (id)) +; + +create table computer ( + id bigint not null, + name varchar(255) not null, + introduced timestamp, + discontinued timestamp, + company_id bigint, + constraint pk_computer primary key (id)) +; + +create sequence company_seq start with 1000; + +create sequence computer_seq start with 1000; + +alter table computer add constraint fk_computer_company_1 foreign key (company_id) references company (id) on delete restrict on update restrict; +create index ix_computer_company_1 on computer (company_id); + + +# --- !Downs + +SET REFERENTIAL_INTEGRITY FALSE; + +drop table if exists company; + +drop table if exists computer; + +SET REFERENTIAL_INTEGRITY TRUE; + +drop sequence if exists company_seq; + +drop sequence if exists computer_seq; + diff --git a/conf/evolutions/default/2.sql b/conf/evolutions/default/2.sql new file mode 100644 index 000000000..a15bd5644 --- /dev/null +++ b/conf/evolutions/default/2.sql @@ -0,0 +1,626 @@ +# --- Sample dataset + +# --- !Ups + +insert into company (id,name) values ( 1,'Apple Inc.'); +insert into company (id,name) values ( 2,'Thinking Machines'); +insert into company (id,name) values ( 3,'RCA'); +insert into company (id,name) values ( 4,'Netronics'); +insert into company (id,name) values ( 5,'Tandy Corporation'); +insert into company (id,name) values ( 6,'Commodore International'); +insert into company (id,name) values ( 7,'MOS Technology'); +insert into company (id,name) values ( 8,'Micro Instrumentation and Telemetry Systems'); +insert into company (id,name) values ( 9,'IMS Associates, Inc.'); +insert into company (id,name) values ( 10,'Digital Equipment Corporation'); +insert into company (id,name) values ( 11,'Lincoln Laboratory'); +insert into company (id,name) values ( 12,'Moore School of Electrical Engineering'); +insert into company (id,name) values ( 13,'IBM'); +insert into company (id,name) values ( 14,'Amiga Corporation'); +insert into company (id,name) values ( 15,'Canon'); +insert into company (id,name) values ( 16,'Nokia'); +insert into company (id,name) values ( 17,'Sony'); +insert into company (id,name) values ( 18,'OQO'); +insert into company (id,name) values ( 19,'NeXT'); +insert into company (id,name) values ( 20,'Atari'); +insert into company (id,name) values ( 22,'Acorn computer'); +insert into company (id,name) values ( 23,'Timex Sinclair'); +insert into company (id,name) values ( 24,'Nintendo'); +insert into company (id,name) values ( 25,'Sinclair Research Ltd'); +insert into company (id,name) values ( 26,'Xerox'); +insert into company (id,name) values ( 27,'Hewlett-Packard'); +insert into company (id,name) values ( 28,'Zemmix'); +insert into company (id,name) values ( 29,'ACVS'); +insert into company (id,name) values ( 30,'Sanyo'); +insert into company (id,name) values ( 31,'Cray'); +insert into company (id,name) values ( 32,'Evans & Sutherland'); +insert into company (id,name) values ( 33,'E.S.R. Inc.'); +insert into company (id,name) values ( 34,'OMRON'); +insert into company (id,name) values ( 35,'BBN Technologies'); +insert into company (id,name) values ( 36,'Lenovo Group'); +insert into company (id,name) values ( 37,'ASUS'); +insert into company (id,name) values ( 38,'Amstrad'); +insert into company (id,name) values ( 39,'Sun Microsystems'); +insert into company (id,name) values ( 40,'Texas Instruments'); +insert into company (id,name) values ( 41,'HTC Corporation'); +insert into company (id,name) values ( 42,'Research In Motion'); +insert into company (id,name) values ( 43,'Samsung Electronics'); + +insert into computer (id,name,introduced,discontinued,company_id) values ( 1,'MacBook Pro 15.4 inch',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 2,'CM-2a',null,null,2); +insert into computer (id,name,introduced,discontinued,company_id) values ( 3,'CM-200',null,null,2); +insert into computer (id,name,introduced,discontinued,company_id) values ( 4,'CM-5e',null,null,2); +insert into computer (id,name,introduced,discontinued,company_id) values ( 5,'CM-5','1991-01-01',null,2); +insert into computer (id,name,introduced,discontinued,company_id) values ( 6,'MacBook Pro','2006-01-10',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 7,'Apple IIe',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 8,'Apple IIc',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 9,'Apple IIGS',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 10,'Apple IIc Plus',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 11,'Apple II Plus',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 12,'Apple III','1980-05-01','1984-04-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 13,'Apple Lisa',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 14,'CM-2',null,null,2); +insert into computer (id,name,introduced,discontinued,company_id) values ( 15,'Connection Machine','1987-01-01',null,2); +insert into computer (id,name,introduced,discontinued,company_id) values ( 16,'Apple II','1977-04-01','1993-10-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 17,'Apple III Plus','1983-12-01','1984-04-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 18,'COSMAC ELF',null,null,3); +insert into computer (id,name,introduced,discontinued,company_id) values ( 19,'COSMAC VIP','1977-01-01',null,3); +insert into computer (id,name,introduced,discontinued,company_id) values ( 20,'ELF II','1977-01-01',null,4); +insert into computer (id,name,introduced,discontinued,company_id) values ( 21,'Macintosh','1984-01-24',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 22,'Macintosh II',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 23,'Macintosh Plus','1986-01-16','1990-10-15',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 24,'Macintosh IIfx',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 25,'iMac','1998-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 26,'Mac Mini','2005-01-22',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 27,'Mac Pro','2006-08-07',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 28,'Power Macintosh','1994-03-01','2006-08-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 29,'PowerBook','1991-01-01','2006-01-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 30,'Xserve',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 31,'Powerbook 100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 32,'Powerbook 140',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 33,'Powerbook 170',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 34,'PowerBook Duo',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 35,'PowerBook 190',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 36,'Macintosh Quadra','1991-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 37,'Macintosh Quadra 900',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 38,'Macintosh Quadra 700',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 39,'Macintosh LC','1990-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 40,'Macintosh LC II','1990-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 41,'Macintosh LC III','1993-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 42,'Macintosh LC III+',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 43,'Macintosh Quadra 605','1993-10-21',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 44,'Macintosh LC 500 series',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 45,'TRS-80 Color Computer','1980-01-01',null,5); +insert into computer (id,name,introduced,discontinued,company_id) values ( 46,'Acorn System 2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 47,'Dragon 32/64',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 48,'MEK6800D2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 49,'Newbear 77/68',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 50,'Commodore PET',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 51,'Commodore 64','1982-08-01','1994-01-01',6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 52,'Commodore 64C',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 53,'Commodore SX-64',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 54,'Commodore 128',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 55,'Apple I','1976-04-01','1977-10-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 56,'KIM-1','1975-01-01',null,7); +insert into computer (id,name,introduced,discontinued,company_id) values ( 57,'Altair 8800','1974-12-19',null,8); +insert into computer (id,name,introduced,discontinued,company_id) values ( 58,'IMSAI 8080','1975-08-01',null,9); +insert into computer (id,name,introduced,discontinued,company_id) values ( 59,'IMSAI Series Two',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 60,'VAX','1977-10-25',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values ( 61,'VAX 11/780','1977-10-25',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values ( 62,'VAX 11/750','1980-10-01',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values ( 63,'TX-2','1958-01-01',null,11); +insert into computer (id,name,introduced,discontinued,company_id) values ( 64,'TX-0','1956-01-01',null,11); +insert into computer (id,name,introduced,discontinued,company_id) values ( 65,'Whirlwind','1951-04-20',null,11); +insert into computer (id,name,introduced,discontinued,company_id) values ( 66,'ENIAC','1946-02-15','1955-10-02',12); +insert into computer (id,name,introduced,discontinued,company_id) values ( 67,'IBM PC','1981-08-12',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values ( 68,'Macintosh Classic',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 69,'Macintosh Classic II','1991-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 70,'Amiga','1985-01-01',null,14); +insert into computer (id,name,introduced,discontinued,company_id) values ( 71,'Amiga 1000',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 72,'Amiga 500','1987-01-01',null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 73,'Amiga 500+',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 74,'Amiga 2000','1986-01-01','1990-01-01',6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 75,'Amiga 3000',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 76,'Amiga 600','1992-03-01',null,6); +insert into computer (id,name,introduced,discontinued,company_id) values ( 77,'Macintosh 128K','1984-01-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 78,'Macintosh 512K','1984-09-10','1986-04-14',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 79,'Macintosh SE','1987-03-02','1989-08-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 80,'Macintosh SE/30','1989-01-19','1991-10-21',1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 81,'Canon Cat','1987-01-01',null,15); +insert into computer (id,name,introduced,discontinued,company_id) values ( 82,'Nokia 770',null,null,16); +insert into computer (id,name,introduced,discontinued,company_id) values ( 83,'Nokia N800','2007-01-01',null,16); +insert into computer (id,name,introduced,discontinued,company_id) values ( 84,'Mylo','2006-09-21',null,17); +insert into computer (id,name,introduced,discontinued,company_id) values ( 85,'OQO 02','2007-01-01',null,18); +insert into computer (id,name,introduced,discontinued,company_id) values ( 86,'OQO 01+',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 87,'Pinwheel calculator',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 88,'iBook',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 89,'MacBook','2006-05-16',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values ( 90,'NeXTstation','1990-01-01','1993-01-01',19); +insert into computer (id,name,introduced,discontinued,company_id) values ( 91,'NeXTcube','1988-01-01','1993-01-01',19); +insert into computer (id,name,introduced,discontinued,company_id) values ( 92,'NeXTstation Color Turbo',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 93,'NeXTstation Color',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 94,'NeXTstation Turbo',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 95,'NeXTcube Turbo',null,null,19); +insert into computer (id,name,introduced,discontinued,company_id) values ( 96,'NeXTcube 040',null,null,19); +insert into computer (id,name,introduced,discontinued,company_id) values ( 97,'NeXTcube 030',null,null,19); +insert into computer (id,name,introduced,discontinued,company_id) values ( 98,'Tinkertoy Tic-Tac-Toe Computer',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values ( 99,'Z3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (100,'Z4',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (101,'Z1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (102,'Z2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (103,'Wang 2200','1973-05-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (104,'Wang VS',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (105,'Wang OIS',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (106,'BBC Micro',null,null,22); +insert into computer (id,name,introduced,discontinued,company_id) values (107,'IBM 650','1953-01-01','1962-01-01',13); +insert into computer (id,name,introduced,discontinued,company_id) values (108,'Cray-1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (109,'Cray-3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (110,'Cray-2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (111,'Cray-4',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (112,'Cray X1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (113,'Cray XD1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (114,'Cray T3D','1993-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (115,'Cray T3E','1995-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (116,'Cray C90',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (117,'Cray T90',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (118,'Cray SV1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (119,'Cray J90',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (120,'Cray XT3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (121,'Cray CS6400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (122,'Atari ST','1985-01-01','1993-01-01',20); +insert into computer (id,name,introduced,discontinued,company_id) values (123,'Amiga 2500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (124,'Amiga 2500',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (125,'Amiga 4000',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (126,'Amiga 3000UX',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (127,'Amiga 3000T',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (128,'Amiga 4000T',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (129,'Amiga 1200','1992-10-01','1996-01-01',6); +insert into computer (id,name,introduced,discontinued,company_id) values (130,'Atari 1040 STf','1986-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (131,'Atari 520 ST','1985-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (132,'Atari 520 STfm','1986-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (133,'Atari 1040 STe','1989-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (134,'Atari MEGA STe','1991-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (135,'Atari 520 ST+','1985-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (136,'Atari 520 STm','1985-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (137,'Atari 130 ST','1985-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (138,'Atari 260 ST','1985-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (139,'Atari MEGA ST','1987-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (140,'Atari 520 STf','1986-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (141,'Atari 1040 STfm','1986-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (142,'Atari 2080 ST','1986-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (143,'Atari 260 ST+','1985-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (144,'Atari 4160 STe','1988-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (145,'TRS-80 Color Computer 2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (146,'TRS-80 Color Computer 3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (147,'TRS-80 Model 1','1977-01-01',null,5); +insert into computer (id,name,introduced,discontinued,company_id) values (148,'Timex Sinclair 2068','1983-11-01','1984-04-01',23); +insert into computer (id,name,introduced,discontinued,company_id) values (149,'ZX Spectrum','1982-01-01',null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (150,'Xerox Star','1981-01-01',null,26); +insert into computer (id,name,introduced,discontinued,company_id) values (151,'Xerox Alto',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (152,'Acorn Archimedes',null,null,22); +insert into computer (id,name,introduced,discontinued,company_id) values (153,'Nintendo Entertainment System',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (154,'Super Nintendo Entertainment System','1991-08-01','1999-01-01',24); +insert into computer (id,name,introduced,discontinued,company_id) values (155,'Super Famicom',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (156,'Nintendo GameCube',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (157,'Game Boy line',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (158,'PlayStation','1994-12-03',null,17); +insert into computer (id,name,introduced,discontinued,company_id) values (159,'PlayStation 2','2000-03-24',null,17); +insert into computer (id,name,introduced,discontinued,company_id) values (160,'Game & Watch',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (161,'EDSAC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (162,'IBM System/4 Pi',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (163,'IBM AP-101',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (164,'IBM TC-1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (165,'IBM AP-101B',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (166,'IBM AP-101S',null,null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (167,'ProLiant',null,null,27); +insert into computer (id,name,introduced,discontinued,company_id) values (168,'Http://nepomuk.semanticdesktop.org/xwiki/',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (169,'Sinclair QL','1984-01-01','1986-01-01',25); +insert into computer (id,name,introduced,discontinued,company_id) values (170,'Sinclair ZX81','1981-01-01',null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (171,'Sinclair ZX80',null,null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (172,'Atari 65XE',null,null,20); +insert into computer (id,name,introduced,discontinued,company_id) values (173,'Deep Blue',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (174,'Macintosh Quadra 650',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (175,'Macintosh Quadra 610',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (176,'Macintosh Quadra 800',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (177,'Macintosh Quadra 950',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (178,'PowerBook 160',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (179,'PowerBook 145B',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (180,'PowerBook 170',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (181,'PowerBook 145',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (182,'PowerBook G3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (183,'PowerBook 140',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (184,'Macintosh IIcx',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (185,'Powerbook 180',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (186,'PowerBook G4',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (187,'Macintosh XL',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (188,'PowerBook 100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (189,'PowerBook 2400c',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (190,'PowerBook 1400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (191,'Macintosh Quadra 630',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (192,'Macintosh Quadra 660AV',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (193,'Macintosh Quadra 840AV',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (194,'PowerBook 5300',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (195,'PowerBook 3400c',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (196,'Macintosh Color Classic',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (197,'Macintosh 512Ke',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (198,'Macintosh IIsi',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (199,'Macintosh IIx',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (200,'PowerBook 500 series',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (201,'Power Macintosh G3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (202,'Macintosh IIci',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (203,'iMac G5','2004-08-31',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (204,'Power Mac G4',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (205,'Power Macintosh 7100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (206,'Power Macintosh 9600',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (207,'Power Macintosh 7200',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (208,'Power Macintosh 7300',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (209,'Power Macintosh 8600',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (210,'Power Macintosh 6200',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (211,'Power Macintosh 8100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (212,'Compact Macintosh',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (213,'Power Macintosh 4400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (214,'Power Macintosh 9500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (215,'Macintosh Portable',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (216,'EMac',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (217,'Power Macintosh 7600',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (218,'Power Mac G5',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (219,'Power Macintosh 7500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (220,'Power Macintosh 6100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (221,'Power Macintosh 8500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (222,'Macintosh IIvi',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (223,'Macintosh IIvx',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (224,'IMac G3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (225,'IMac G4',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (226,'Power Mac G4 Cube',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (227,'Intel iMac',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (228,'Deep Thought',null,null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (229,'Wii','2006-11-19',null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (230,'IBM System x',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (231,'IBM System i','2006-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (232,'IBM System z','2006-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (233,'IBM System p','2000-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (234,'LC 575',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (235,'Macintosh TV',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (236,'Macintosh Performa',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (237,'Macintosh II series',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (238,'Power Macintosh 6400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (239,'Power Macintosh 6500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (240,'Apple PenLite',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (241,'Wallstreet',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (242,'Twentieth Anniversary Macintosh',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (243,'Power Macintosh 5500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (244,'iBook G3',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (245,'Power Macintosh 5200 LC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (246,'Power Macintosh 5400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (247,'CM-1',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (248,'MSX','1983-01-01','1995-01-01',28); +insert into computer (id,name,introduced,discontinued,company_id) values (249,'PlayStation 3',null,null,17); +insert into computer (id,name,introduced,discontinued,company_id) values (250,'MSX2','1986-01-01',null,29); +insert into computer (id,name,introduced,discontinued,company_id) values (251,'MSX2+','1988-01-01',null,30); +insert into computer (id,name,introduced,discontinued,company_id) values (252,'MSX turbo R','1990-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (253,'Panasonic FS A1GT',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (254,'Panasonic FS A1ST',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (255,'PDP-11',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (256,'PDP-1',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (257,'PDP-10',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (258,'PDP-8',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (259,'PDP-6',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (260,'DECSYSTEM-20',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (261,'PDP-7',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (262,'PDP-5',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (263,'PDP-12',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (264,'LINC',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (265,'PDP-14',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (266,'PDP-15',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (267,'PDP-16',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (268,'Cray X2','2007-01-01',null,31); +insert into computer (id,name,introduced,discontinued,company_id) values (269,'Cray X-MP','1982-01-01',null,31); +insert into computer (id,name,introduced,discontinued,company_id) values (270,'Evans & Sutherland ES-1',null,null,32); +insert into computer (id,name,introduced,discontinued,company_id) values (271,'Commodore VIC-20','1980-01-01',null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (272,'PowerBook 150',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (273,'MacBook Air','2008-01-15',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (274,'Digi-Comp I','1963-01-01',null,33); +insert into computer (id,name,introduced,discontinued,company_id) values (275,'Digi-Comp',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (276,'Digi-Comp II',null,null,33); +insert into computer (id,name,introduced,discontinued,company_id) values (277,'Manchester Mark I','1949-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (278,'Small-Scale Experimental Machine','1948-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (279,'Nintendo 64',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (280,'Game Boy Advance',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (281,'Game Boy',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (282,'Nintendo DS Lite',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (283,'Nintendo DS','2004-01-01',null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (284,'Game Boy Color',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (285,'Game Boy Advance SP',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (286,'Virtual Boy',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (287,'Game Boy Micro',null,null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (288,'Roadrunner',null,null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (289,'HP 9000',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (290,'OMRON Luna-88K2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (291,'OMRON Luna-88K',null,null,34); +insert into computer (id,name,introduced,discontinued,company_id) values (292,'Motorola series 900',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (293,'Motorola M8120',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (294,'Triton Dolphin System 100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (295,'BBN TC2000','1989-08-01',null,35); +insert into computer (id,name,introduced,discontinued,company_id) values (296,'WRT54G',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (297,'ThinkPad','1992-01-01',null,36); +insert into computer (id,name,introduced,discontinued,company_id) values (298,'Apple Newton','1993-01-01','1998-01-01',1); +insert into computer (id,name,introduced,discontinued,company_id) values (299,'Atanasoff-Berry Computer','1937-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (300,'Atlas Computer','1962-01-01','1974-01-01',null); +insert into computer (id,name,introduced,discontinued,company_id) values (301,'ASUS Eee PC 901',null,null,37); +insert into computer (id,name,introduced,discontinued,company_id) values (302,'ASUS Eee PC 701',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (303,'IBM 7030','1961-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (304,'System/38','1979-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (305,'System/36','1983-01-01','2000-01-01',13); +insert into computer (id,name,introduced,discontinued,company_id) values (306,'IBM 7090','1959-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (307,'IBM RT',null,null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (308,'System/360','1964-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (309,'IBM 801','1980-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (310,'IBM 1401','1959-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (311,'ASCI White','2001-01-01','2006-01-01',13); +insert into computer (id,name,introduced,discontinued,company_id) values (312,'Blue Gene',null,null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (313,'ASCI Blue Pacific','1998-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (314,'iPhone','2007-06-01',null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (315,'Nokia N810','2007-10-17',null,16); +insert into computer (id,name,introduced,discontinued,company_id) values (316,'EDSAC 2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (317,'Titan',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (318,'Pilot ACE',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (319,'HP Mini 1000','2008-10-29',null,27); +insert into computer (id,name,introduced,discontinued,company_id) values (320,'HP 2133 Mini-Note PC','2008-04-15',null,27); +insert into computer (id,name,introduced,discontinued,company_id) values (321,'Kogan Agora Pro','2008-12-04',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (322,'D-Series Machines',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (323,'ZX Spectrum 48K','1982-01-01',null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (324,'ZX Spectrum 16K','1982-01-01',null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (325,'ZX Spectrum 128','1985-09-01',null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (326,'ZX Spectrum +3',null,null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (327,'ZX Spectrum +2','1986-01-01',null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (328,'ZX Spectrum +2A','1987-01-01',null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (329,'ZX Spectrum +','1984-06-01',null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (330,'Acer Extensa',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (331,'Acer Extensa 5220',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (332,'Dell Latitude',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (333,'Toshiba Satellite',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (334,'Timex Sinclair 2048',null,null,23); +insert into computer (id,name,introduced,discontinued,company_id) values (335,'Sprinter',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (336,'Timex Computer 2048',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (337,'Pentagon',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (338,'Belle',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (339,'Loki',null,null,25); +insert into computer (id,name,introduced,discontinued,company_id) values (340,'Hobbit',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (341,'NeXT Computer',null,null,19); +insert into computer (id,name,introduced,discontinued,company_id) values (342,'TRS-80',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (343,'TRS-80 Model 2','1980-01-01',null,5); +insert into computer (id,name,introduced,discontinued,company_id) values (344,'TRS-80 Model 3',null,null,5); +insert into computer (id,name,introduced,discontinued,company_id) values (345,'STacy','1989-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (346,'ST BOOK','1990-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (347,'Atari 520 STE','1989-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (348,'Amiga 2000 Model A',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (349,'Amiga 2000 Model B',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (350,'Amiga 2000 Model C',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (351,'IBM 3270',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (352,'CALDIC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (353,'Modbook',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (354,'Compaq SystemPro',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (355,'ARRA',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (356,'IBM System Cluster 1350',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (357,'Finite element machine',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (358,'ES7000',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (359,'HP MediaSmart Server',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (360,'HP Superdome',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (361,'IBM Power Systems','2008-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (362,'Oslo Analyzer',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (363,'Microsoft Softcard',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (364,'WITCH',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (365,'Analytical engine',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (366,'EDVAC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (367,'BINAC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (368,'Earth Simulator',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (369,'BARK',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (370,'Harvard Mark I','1944-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (371,'ILLIAC IV',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (372,'ILLIAC II',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (373,'ILLIAC III',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (374,'Water integrator',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (375,'CSIRAC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (376,'System X',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (377,'Harvest',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (378,'ChipTest',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (379,'HiTech',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (380,'Bomba',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (381,'ACE',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (382,'ASCI Red',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (383,'ASCI Thors Hammer',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (384,'ASCI Purple','2005-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (385,'ASCI Blue Mountain',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (386,'Columbia',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (387,'HP Integrity',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (388,'APEXC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (389,'Datasaab D2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (390,'BRLESC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (391,'DYSEAC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (392,'SSEC','1948-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (393,'Hydra',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (394,'FUJIC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (395,'RAYDAC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (396,'Harvard Mark III',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (397,'DATAR',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (398,'ReserVec',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (399,'DASK',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (400,'UTEC',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (401,'DRTE Computer',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (402,'PowerEdge',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (403,'Apple Network Server',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (404,'Goodyear MPP',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (405,'Macintosh 128K technical details',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (406,'Power Macintosh G3',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (407,'CER-10',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (408,'CER-20',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (409,'IBM BladeCenter','2002-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (410,'Wisconsin Integrally Synchronized Computer',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (411,'Amstrad CPC',null,null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (412,'Amstrad CPC 6128',null,null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (413,'Amstrad CPC 664',null,null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (414,'Amstrad CPC 464',null,null,38); +insert into computer (id,name,introduced,discontinued,company_id) values (415,'Intergraph',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (416,'Enterprise',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (417,'MTX500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (418,'Acorn Electron',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (419,'Sony Vaio P','2009-02-01',null,17); +insert into computer (id,name,introduced,discontinued,company_id) values (420,'VAIO',null,null,17); +insert into computer (id,name,introduced,discontinued,company_id) values (421,'Sony Vaio P VGN-P588E/Q',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (422,'Sony Vaio P VGN-P530H/G',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (423,'Sony Vaio P VGN-P530H/W',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (424,'Sony Vaio P VGN-P530H/Q',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (425,'Sony Vaio P VGN-P530H/R',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (426,'Sony Vaio P VGN-P588E/R',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (427,'Sony Vaio P VGN-P598E/Q',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (428,'Timex Sinclair 1000','1982-07-01',null,23); +insert into computer (id,name,introduced,discontinued,company_id) values (429,'Komputer 2086',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (430,'Galaksija',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (431,'Vector-06C',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (432,'Elektronika BK',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (433,'Sun386i',null,null,39); +insert into computer (id,name,introduced,discontinued,company_id) values (434,'Xerox Daybreak','1985-01-01','1989-01-01',null); +insert into computer (id,name,introduced,discontinued,company_id) values (435,'Xerox NoteTaker',null,null,26); +insert into computer (id,name,introduced,discontinued,company_id) values (436,'D4a','1965-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (437,'LGP-30',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (438,'LGP-21',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (439,'ASUS Eee PC 900','2008-05-01',null,37); +insert into computer (id,name,introduced,discontinued,company_id) values (440,'Atari TT030',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (441,'Bi Am ZX-Spectrum 48/64',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (442,'Bi Am ZX-Spectrum 128',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (443,'PlayStation Portable',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (444,'MSI Wind Netbook',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (445,'Sharp Mebius NJ70A','2009-04-21',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (446,'HTC Snap',null,null,41); +insert into computer (id,name,introduced,discontinued,company_id) values (447,'Commodore Educator 64',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (448,'Amiga 1500',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (449,'Commodore 65',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (450,'Commodore 16',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (451,'Commodore CBM-II',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (452,'Commodore Plus/4',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (453,'Commodore LCD',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (454,'Commodore MAX Machine',null,null,6); +insert into computer (id,name,introduced,discontinued,company_id) values (455,'Aster CT-80',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (456,'Test','2009-01-01','2009-01-01',null); +insert into computer (id,name,introduced,discontinued,company_id) values (457,'MSI GX723',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (458,'Eee PC 1000HV','2009-05-22',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (459,'VTech Laser 200','1983-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (460,'CrunchPad',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (461,'Neo Geo','1990-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (462,'Sega Mega Drive',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (463,'Sega Master System',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (464,'TurboGrafx-16',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (465,'Sun-3',null,null,39); +insert into computer (id,name,introduced,discontinued,company_id) values (466,'Pleiades',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (467,'IBM Sequoia',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (468,'Inves Spectrum 48k plus',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (469,'iPhone 3G',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (470,'iPhone 3GS',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (471,'Beagle Board',null,null,40); +insert into computer (id,name,introduced,discontinued,company_id) values (472,'HP nPar',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (473,'MacBook Family',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (474,'Reservisor',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (475,'BladeSystem',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (476,'lenovo thinkpad t60p',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (477,'lenovo thinkpad x200',null,null,36); +insert into computer (id,name,introduced,discontinued,company_id) values (478,'lenovo thinkpad t60',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (479,'lenovo thinkpad w700',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (480,'lenovo thinkpad t41',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (481,'lenovo thinkpad z61p',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (482,'lenovo thinkpad x61s',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (483,'lenovo thinkpad t43',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (484,'lenovo thinkpad r400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (485,'lenovo thinkpad x60s',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (486,'lenovo thinkpad x301',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (487,'lenovo thinkpad t42',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (488,'lenovo thinkpad r61',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (489,'lenovo thinkpad w500',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (490,'lenovo thinkpad sl400',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (491,'lenovo thinkpad x40',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (492,'lenovo thinkpad x200 tablet',null,null,36); +insert into computer (id,name,introduced,discontinued,company_id) values (493,'lenovo thinkpad t400s',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (494,'Nokia N900','2009-10-01',null,16); +insert into computer (id,name,introduced,discontinued,company_id) values (495,'Internet Tablet',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (496,'Meiko Computing Surface','1986-01-01','1993-01-01',null); +insert into computer (id,name,introduced,discontinued,company_id) values (497,'CS-2',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (498,'IBM 701','1952-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (499,'IBM 5100','1975-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (500,'AN/FSQ-7','1958-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (501,'AN/FSQ-32','1960-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (502,'IBM CPC','1949-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (503,'System/34','1978-01-01','1983-01-01',13); +insert into computer (id,name,introduced,discontinued,company_id) values (504,'System/32','1975-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (505,'System/3','1969-01-01','1985-01-01',13); +insert into computer (id,name,introduced,discontinued,company_id) values (506,'IBM 305','1956-01-01',null,13); +insert into computer (id,name,introduced,discontinued,company_id) values (507,'English Electric DEUCE',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (508,'CER-203',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (509,'CER-22',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (510,'Kentucky Linux Athlon Testbed',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (511,'QNAP TS-101',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (512,'iPad','2010-01-01','2011-03-02',1); +insert into computer (id,name,introduced,discontinued,company_id) values (513,'iPhone 2G',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (514,'Inslaw',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (515,'WePad','2010-07-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (516,'MacBook Parts',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (517,'MacBook 13-inch Core 2 Duo 2.13GHz (MC240LL/A) DDR2 Model',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (518,'MacBook 13-inch Core 2 Duo 2.13GHz (MC240T/A) DDR2 Model',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (519,'MacBook 13-inch Core 2 Duo 2.13GHz (MC240X/A) DDR2 Model',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (520,'MacBook 13-inch Core 2 Duo 2.26GHz (Unibody MC207LL/A) DDR3 Model',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (521,'MC240LL/A',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (522,'D.K.COMMUNICATION',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (523,'iPhone 4',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (524,'Nintendo 3DS','2010-03-23',null,24); +insert into computer (id,name,introduced,discontinued,company_id) values (525,'ASUS Eee PC 1005PE','2010-01-01',null,37); +insert into computer (id,name,introduced,discontinued,company_id) values (526,'National Law Enforcement System',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (527,'BlackBerry PlayBook',null,null,42); +insert into computer (id,name,introduced,discontinued,company_id) values (528,'Barnes & Noble nook','2009-10-20',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (529,'SAM Coupé',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (530,'HTC Dream','2008-10-22',null,41); +insert into computer (id,name,introduced,discontinued,company_id) values (531,'Samsung Galaxy Tab','2010-09-02',null,43); +insert into computer (id,name,introduced,discontinued,company_id) values (532,'BlackBerry PlayBook','2010-09-27',null,42); +insert into computer (id,name,introduced,discontinued,company_id) values (533,'Tianhe-I',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (534,'Kno',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (535,'ThinkPad 701 C',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (536,'ThinkPad 340 CSE',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (537,'ThinkPad 755 CX',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (538,'ThinkPad 755 CE',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (539,'ThinkPad 370 C',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (540,'Coleco Adam','1983-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (541,'Nebulae',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (542,'Alex eReader',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (543,'Acer Iconia',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (544,'Archos 101',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (545,'Fujitsu Lifebook T900',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (546,'Motorola Xoom',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (547,'ViewSonic G Tablet',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (548,'DEC Professional','1982-01-01',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (549,'DEC Multia','1994-11-07',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (550,'DEC Firefly',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (551,'DEC 3000 AXP',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (552,'DEC 2000 AXP','1993-05-25',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (553,'DEC 4000 AXP','1992-11-10',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (554,'DEC 7000/10000 AXP','1992-11-10',null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (555,'DEC Professional 350',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (556,'DEC Rainbow 100',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (557,'DEC Professional 325',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (558,'DECmate II',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (559,'DECmate',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (560,'DECsystem',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (561,'NetApp Filer',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (562,'DEC GT40',null,null,10); +insert into computer (id,name,introduced,discontinued,company_id) values (563,'ecoATM',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (564,'MindWave BrainCubed Education Bundle',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (565,'PalmPilot',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (566,'Upcoming iPhone 5',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (567,'Dell Inspiron 560 Desktop Computer ',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (568,'IPad 2',null,null,1); +insert into computer (id,name,introduced,discontinued,company_id) values (569,'HP TouchPad','2011-02-09',null,27); +insert into computer (id,name,introduced,discontinued,company_id) values (570,'HP Veer','2011-02-09',null,27); +insert into computer (id,name,introduced,discontinued,company_id) values (571,'Lenovo Thinkpad Edge 11',null,null,36); +insert into computer (id,name,introduced,discontinued,company_id) values (572,'Dell Vostro',null,null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (573,'Gateway LT3103U','2008-01-01',null,null); +insert into computer (id,name,introduced,discontinued,company_id) values (574,'iPhone 4S','2011-10-14',null,1); + +# --- !Downs + +delete from computer; +delete from company; diff --git a/conf/messages b/conf/messages new file mode 100644 index 000000000..c35175b48 --- /dev/null +++ b/conf/messages @@ -0,0 +1,3 @@ +# Messages + +computers.list.title={0,choice,0#No computers|1#One computer|1<{0,number,integer} computers} found diff --git a/conf/routes b/conf/routes new file mode 100644 index 000000000..57deb0736 --- /dev/null +++ b/conf/routes @@ -0,0 +1,24 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + +# Default path will just redirect to the computer list +GET / controllers.Application.index + +# Computers list (look at the default values for pagination parameters) +GET /computers controllers.Application.list(p:Int ?= 0, s:Int ?= 2, f ?= "") + +# Add computer +GET /computers/new controllers.Application.create +POST /computers controllers.Application.save + +# Edit existing computer +GET /computers/:id controllers.Application.edit(id:Long) +POST /computers/:id controllers.Application.update(id:Long) + +# Delete a computer +POST /computers/:id/delete controllers.Application.delete(id:Long) + +# Map static resources from the /public folder to the /assets URL path +GET /assets/*file controllers.Assets.at(path="/public", file) + diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..be6c454fb --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.5 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 000000000..d6ac63ab4 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,20 @@ +resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" + +// The Play plugin +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.1") + +// web plugins + +addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.1") + +addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-mocha" % "1.0.0") diff --git a/public/stylesheets/bootstrap.min.css b/public/stylesheets/bootstrap.min.css new file mode 100644 index 000000000..bf4532834 --- /dev/null +++ b/public/stylesheets/bootstrap.min.css @@ -0,0 +1,330 @@ +html,body{margin:0;padding:0;} +h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} +table{border-collapse:collapse;border-spacing:0;} +ol,ul{list-style:none;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted;} +a:hover,a:active{outline:0;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{border:0;-ms-interpolation-mode:bicubic;} +button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} +button,input{line-height:normal;*overflow:visible;} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +html,body{background-color:#ffffff;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} +.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;*display:inline;} +.container:after{clear:both;} +.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;*display:inline;} +.container-fluid:after{clear:both;} +.container-fluid>.sidebar{float:left;width:220px;} +.container-fluid>.content{margin-left:240px;} +a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;*display:inline;} +.row:after{clear:both;} +[class*="span"]{display:inline;float:left;margin-left:20px;} +.span1{width:40px;} +.span2{width:100px;} +.span3{width:160px;} +.span4{width:220px;} +.span5{width:280px;} +.span6{width:340px;} +.span7{width:400px;} +.span8{width:460px;} +.span9{width:520px;} +.span10{width:580px;} +.span11{width:640px;} +.span12{width:700px;} +.span13{width:760px;} +.span14{width:820px;} +.span15{width:880px;} +.span16{width:940px;} +.span17{width:1000px;} +.span18{width:1060px;} +.span19{width:1120px;} +.span20{width:1180px;} +.span21{width:1240px;} +.span22{width:1300px;} +.span23{width:1360px;} +.span24{width:1420px;} +.offset1{margin-left:80px;} +.offset2{margin-left:140px;} +.offset3{margin-left:200px;} +.offset4{margin-left:260px;} +.offset5{margin-left:320px;} +.offset6{margin-left:380px;} +.offset7{margin-left:440px;} +.offset8{margin-left:500px;} +.offset9{margin-left:560px;} +.offset10{margin-left:620px;} +.offset11{margin-left:680px;} +.offset12{margin-left:740px;} +.span-one-third{width:300px;} +.span-two-thirds{width:620px;} +.offset-one-third{margin-left:340px;} +.offset-two-thirds{margin-left:660px;} +p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} +h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} +h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} +h3,h4,h5,h6{line-height:36px;} +h3{font-size:18px;}h3 small{font-size:14px;} +h4{font-size:16px;}h4 small{font-size:12px;} +h5{font-size:14px;} +h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} +ul,ol{margin:0 0 18px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;color:#808080;} +ul.unstyled{list-style:none;margin-left:0;} +dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} +dl dt{font-weight:bold;} +dl dd{margin-left:9px;} +hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} +strong{font-style:inherit;font-weight:bold;} +em{font-style:italic;font-weight:inherit;line-height:inherit;} +.muted{color:#bfbfbf;} +blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} +blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} +address{display:block;line-height:18px;margin-bottom:18px;} +code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} +pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} +form{margin-bottom:18px;} +fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} +form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;*display:inline;} +form .clearfix:after{clear:both;} +label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} +label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} +form .input{margin-left:150px;} +input[type=checkbox],input[type=radio]{cursor:pointer;} +input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} +input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} +select,input[type=file]{height:27px;line-height:27px;*margin-top:4px;} +select[multiple]{height:inherit;} +textarea{height:auto;} +.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +:-moz-placeholder{color:#bfbfbf;} +::-webkit-input-placeholder{color:#bfbfbf;} +input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} +input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} +input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} +form div.clearfix.error{background:#fae5e3;padding:10px 0;margin:-10px 0 10px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}form div.clearfix.error>label,form div.clearfix.error span.help-inline,form div.clearfix.error span.help-block{color:#9d261d;} +form div.clearfix.error input,form div.clearfix.error textarea{border-color:#c87872;-webkit-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);-moz-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);box-shadow:0 0 3px rgba(171, 41, 32, 0.25);}form div.clearfix.error input:focus,form div.clearfix.error textarea:focus{border-color:#b9554d;-webkit-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);-moz-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);box-shadow:0 0 6px rgba(171, 41, 32, 0.5);} +form div.clearfix.error .input-prepend span.add-on,form div.clearfix.error .input-append span.add-on{background:#f4c8c5;border-color:#c87872;color:#b9554d;} +.input-mini,input.mini,textarea.mini,select.mini{width:60px;} +.input-small,input.small,textarea.small,select.small{width:90px;} +.input-medium,input.medium,textarea.medium,select.medium{width:150px;} +.input-large,input.large,textarea.large,select.large{width:210px;} +.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} +.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} +textarea.xxlarge{overflow-y:auto;} +input.span1,textarea.span1,select.span1{display:inline-block;float:none;width:30px;margin-left:0;} +input.span2,textarea.span2,select.span2{display:inline-block;float:none;width:90px;margin-left:0;} +input.span3,textarea.span3,select.span3{display:inline-block;float:none;width:150px;margin-left:0;} +input.span4,textarea.span4,select.span4{display:inline-block;float:none;width:210px;margin-left:0;} +input.span5,textarea.span5,select.span5{display:inline-block;float:none;width:270px;margin-left:0;} +input.span6,textarea.span6,select.span6{display:inline-block;float:none;width:330px;margin-left:0;} +input.span7,textarea.span7,select.span7{display:inline-block;float:none;width:390px;margin-left:0;} +input.span8,textarea.span8,select.span8{display:inline-block;float:none;width:450px;margin-left:0;} +input.span9,textarea.span9,select.span9{display:inline-block;float:none;width:510px;margin-left:0;} +input.span10,textarea.span10,select.span10{display:inline-block;float:none;width:570px;margin-left:0;} +input.span11,textarea.span11,select.span11{display:inline-block;float:none;width:630px;margin-left:0;} +input.span12,textarea.span12,select.span12{display:inline-block;float:none;width:690px;margin-left:0;} +input.span13,textarea.span13,select.span13{display:inline-block;float:none;width:750px;margin-left:0;} +input.span14,textarea.span14,select.span14{display:inline-block;float:none;width:810px;margin-left:0;} +input.span15,textarea.span15,select.span15{display:inline-block;float:none;width:870px;margin-left:0;} +input.span16,textarea.span16,select.span16{display:inline-block;float:none;width:930px;margin-left:0;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} +.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} +.help-inline,.help-block{font-size:11px;line-height:18px;color:#bfbfbf;} +.help-inline{padding-left:5px;*position:relative;*top:-5px;} +.help-block{display:block;max-width:600px;} +.inline-inputs{color:#808080;}.inline-inputs span,.inline-inputs input{display:inline-block;} +.inline-inputs input.mini{width:60px;} +.inline-inputs input.small{width:90px;} +.inline-inputs span{padding:0 2px 0 1px;} +.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} +.input-prepend .add-on{*margin-top:1px;} +.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} +.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} +.inputs-list label{display:block;float:none;width:auto;padding:0;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} +.inputs-list label small{font-size:11px;font-weight:normal;} +.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} +.inputs-list:first-child{padding-top:6px;} +.inputs-list li+li{padding-top:2px;} +.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;} +.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} +.form-stacked legend{padding-left:0;} +.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} +.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} +.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} +.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} +.form-stacked .actions{margin-left:-20px;padding-left:20px;} +table{width:100%;margin-bottom:18px;padding:0;border-collapse:separate;*border-collapse:collapse;font-size:13px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} +table th{padding-top:9px;font-weight:bold;vertical-align:middle;border-bottom:1px solid #ddd;} +table td{vertical-align:top;} +table th+th,table td+td{border-left:1px solid #ddd;} +table tr+tr td{border-top:1px solid #ddd;} +table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} +table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} +table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} +table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} +.zebra-striped tbody tr:nth-child(odd) td{background-color:#f9f9f9;} +.zebra-striped tbody tr:hover td{background-color:#f5f5f5;} +table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} +table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} +table .header:hover:after{visibility:visible;} +table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .blue{color:#049cdb;border-bottom-color:#049cdb;} +table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} +table .green{color:#46a546;border-bottom-color:#46a546;} +table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} +table .red{color:#9d261d;border-bottom-color:#9d261d;} +table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} +table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} +table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} +table .orange{color:#f89406;border-bottom-color:#f89406;} +table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} +table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} +table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} +.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.topbar h3 a:hover,.topbar .brand a:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;} +.topbar h3{position:relative;} +.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;} +.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;} +.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.topbar form.pull-right{float:right;} +.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;} +.topbar input::-webkit-input-placeholder{color:#e6e6e6;} +.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;} +.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} +.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} +.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;} +.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;} +.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);} +.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;} +.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;} +.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;} +.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;} +.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;} +li.menu,.dropdown{position:relative;} +a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;} +.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;} +.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);} +.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} +.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;} +.tabs,.pills{margin:0 0 20px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;*display:inline;} +.tabs:after,.pills:after{clear:both;} +.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;} +.tabs{float:left;width:100%;border-bottom:1px solid #ddd;}.tabs>li{position:relative;top:1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:36px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;} +.tabs>li.active>a{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;} +.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;} +.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;} +.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;} +.tab-content{clear:both;} +.pills a{margin:5px 3px 5px 0;padding:0 15px;text-shadow:0 1px 1px #ffffff;line-height:30px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{background:#00438a;color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} +.pills .active a{background:#0069d6;color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} +.tab-content>*,.pill-content>*{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.breadcrumb{margin:0 0 18px;padding:7px 14px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#bfbfbf;} +.breadcrumb .active a{color:#404040;} +.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;} +footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} +.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} +.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} +.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} +.btn:focus{outline:1px dotted #666;} +.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.btn.small{padding:7px 9px 7px;font-size:11px;} +:root .alert-message,:root .btn{border-radius:0 \0;} +button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} +.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);-khtml-opacity:0.2;-moz-opacity:0.2;opacity:0.2;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} +.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{*margin-top:3px;} +.alert-message h5{line-height:18px;} +.alert-message p{margin-bottom:0;} +.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;} +.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);} +.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;} +.alert-message.block-message ul{margin-bottom:0;} +.alert-message.block-message li{color:#404040;} +.alert-message.block-message .alert-actions{margin-top:5px;} +.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;} +.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;} +.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;} +.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} +.pagination a:hover,.pagination .active a{background-color:#c7eefe;} +.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} +.pagination .next a{border:0;} +.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -250px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} +.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{border-bottom:1px solid #eee;padding:5px 15px;} +.modal-body{padding:15px;} +.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;*display:inline;} +.modal-footer:after{clear:both;} +.modal-footer .btn{float:right;margin-left:5px;} +.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.twipsy-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover .inner{background-color:#000000;background-color:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;} +.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} +.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} +.label{padding:1px 3px 2px;background-color:#bfbfbf;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} +.label.warning{background-color:#f89406;} +.label.success{background-color:#46a546;} +.label.notice{background-color:#62cffc;} +.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;*display:inline;} +.media-grid:after{clear:both;} +.media-grid li{display:inline;} +.media-grid a{float:left;padding:4px;margin:0 0 20px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;} +.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} \ No newline at end of file diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css new file mode 100644 index 000000000..4f0a4165a --- /dev/null +++ b/public/stylesheets/main.css @@ -0,0 +1,65 @@ +html { + background: #eee; +} + +header h1 { + padding: 0.4em 1.1em; + color: white; + font-weight: normal; + font-size: 24px; +} + +section#main { + position: relative; + padding: 5em 2em; + border-bottom: 1px solid #ccc; + min-height: 600px; +} + +section#main .topRight { + position: absolute; + right: 20px; + top: 70px; +} + +table.computers em { + color: #aaa; +} + +table.computers .col3, table.computers .col4 { + width: 10%; + min-width: 100px; +} + +table.computers .col5 { + width: 30%; + min-width: 300px; +} + +table.computers .header a { +} + +#actions { + position: relative; +} + +#actions #add { + position: absolute; + right: 0; + top: 0; +} + +#pagination { + position: relative; +} + +#pagination ul { + position: absolute; + right: 0; +} + +#pagination ul .current a { + color: #666; +} + + diff --git a/test/ApplicationSpec.scala b/test/ApplicationSpec.scala new file mode 100644 index 000000000..559895b89 --- /dev/null +++ b/test/ApplicationSpec.scala @@ -0,0 +1,87 @@ +import org.specs2.mutable._ +import org.specs2.runner._ +import org.junit.runner._ + +import play.api.test._ +import play.api.test.Helpers._ + +@RunWith(classOf[JUnitRunner]) +class ApplicationSpec extends Specification { + + import models._ + + // -- Date helpers + + def dateIs(date: java.util.Date, str: String) = new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str + + // -- + + "Application" should { + + "redirect to the computer list on /" in { + + val result = controllers.Application.index(FakeRequest()) + + status(result) must equalTo(SEE_OTHER) + redirectLocation(result) must beSome.which(_ == "/computers") + + } + + "list computers on the the first page" in { + running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + val result = controllers.Application.list(0, 2, "")(FakeRequest()) + + status(result) must equalTo(OK) + contentAsString(result) must contain("574 computers found") + + } + } + + "filter computer by name" in { + running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + val result = controllers.Application.list(0, 2, "Apple")(FakeRequest()) + + status(result) must equalTo(OK) + contentAsString(result) must contain("13 computers found") + + } + } + + "create new computer" in { + running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + val badResult = controllers.Application.save(FakeRequest()) + + status(badResult) must equalTo(BAD_REQUEST) + + val badDateFormat = controllers.Application.save( + FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "badbadbad", "company" -> "1") + ) + + status(badDateFormat) must equalTo(BAD_REQUEST) + contentAsString(badDateFormat) must contain("""""") + contentAsString(badDateFormat) must contain("""""") + contentAsString(badDateFormat) must contain("""""") + + + val result = controllers.Application.save( + FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "2011-12-24", "company" -> "1") + ) + + status(result) must equalTo(SEE_OTHER) + redirectLocation(result) must beSome.which(_ == "/computers") + flash(result).get("success") must beSome.which(_ == "Computer FooBar has been created") + + val list = controllers.Application.list(0, 2, "FooBar")(FakeRequest()) + + status(list) must equalTo(OK) + contentAsString(list) must contain("One computer found") + + } + } + + } + +} \ No newline at end of file diff --git a/test/IntegrationSpec.scala b/test/IntegrationSpec.scala new file mode 100644 index 000000000..562ee68fb --- /dev/null +++ b/test/IntegrationSpec.scala @@ -0,0 +1,66 @@ +import org.specs2.mutable._ +import org.specs2.runner._ +import org.junit.runner._ + +import play.api.test._ +import play.api.test.Helpers._ + +import org.fluentlenium.core.filter.FilterConstructor._ + +@RunWith(classOf[JUnitRunner]) +class IntegrationSpec extends Specification { + + "Application" should { + + "work from within a browser" in { + running(TestServer(3333), HTMLUNIT) { browser => + browser.goTo("http://localhost:3333/") + + browser.$("header h1").first.getText must equalTo("Play sample application — Computer database") + browser.$("section h1").first.getText must equalTo("574 computers found") + + browser.$("#pagination li.current").first.getText must equalTo("Displaying 1 to 10 of 574") + + browser.$("#pagination li.next a").click() + + browser.$("#pagination li.current").first.getText must equalTo("Displaying 11 to 20 of 574") + browser.$("#searchbox").text("Apple") + browser.$("#searchsubmit").click() + + browser.$("section h1").first.getText must equalTo("13 computers found") + browser.$("a", withText("Apple II")).click() + + browser.$("section h1").first.getText must equalTo("Edit computer") + + browser.$("#discontinued").text("xxx") + browser.$("input.primary").click() + + browser.$("div.error").size must equalTo(1) + browser.$("div.error label").first.getText must equalTo("Discontinued date") + + browser.$("#discontinued").text("") + browser.$("input.primary").click() + + browser.$("section h1").first.getText must equalTo("574 computers found") + browser.$(".alert-message").first.getText must equalTo("Done! Computer Apple II has been updated") + + browser.$("#searchbox").text("Apple") + browser.$("#searchsubmit").click() + + browser.$("a", withText("Apple II")).click() + browser.$("input.danger").click() + + browser.$("section h1").first.getText must equalTo("573 computers found") + browser.$(".alert-message").first.getText must equalTo("Done! Computer has been deleted") + + browser.$("#searchbox").text("Apple") + browser.$("#searchsubmit").click() + + browser.$("section h1").first.getText must equalTo("12 computers found") + + } + } + + } + +} \ No newline at end of file diff --git a/test/ModelSpec.scala b/test/ModelSpec.scala new file mode 100644 index 000000000..e0656fc69 --- /dev/null +++ b/test/ModelSpec.scala @@ -0,0 +1,58 @@ +import org.specs2.mutable._ +import org.specs2.runner._ +import org.junit.runner._ + +import play.api.test._ +import play.api.test.Helpers._ + +@RunWith(classOf[JUnitRunner]) +class ModelSpec extends Specification { + + import models._ + + // -- Date helpers + + def dateIs(date: java.util.Date, str: String) = new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str + + // -- + + "Computer model" should { + + "be retrieved by id" in { + running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + val Some(macintosh) = Computer.findById(21) + + macintosh.name must equalTo("Macintosh") + macintosh.introduced must beSome.which(dateIs(_, "1984-01-24")) + + } + } + + "be listed along its companies" in { + running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + val computers = Computer.list() + + computers.total must equalTo(574) + computers.items must have length(10) + + } + } + + "be updated if needed" in { + running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + Computer.update(21, Computer(name="The Macintosh", introduced=None, discontinued=None, companyId=Some(1))) + + val Some(macintosh) = Computer.findById(21) + + macintosh.name must equalTo("The Macintosh") + macintosh.introduced must beNone + + } + } + + } + +} \ No newline at end of file diff --git a/tutorial/index.html b/tutorial/index.html new file mode 100644 index 000000000..3d3fb75ad --- /dev/null +++ b/tutorial/index.html @@ -0,0 +1,14 @@ + + + + Computer Database + + +
+

Computer Database

+

+ This is a classic CRUD application, backed by a JDBC database. It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). +

+
+ + \ No newline at end of file From d0ea0a60e21087ccc694f0cead5e5441458d643f Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Tue, 15 Jul 2014 20:36:19 -0700 Subject: [PATCH 02/68] Minor polish of metadata --- README | 6 ++---- activator.properties | 6 +++--- test/ApplicationSpec.scala | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/README b/README index 066d62a38..fae03678b 100644 --- a/README +++ b/README @@ -1,7 +1,5 @@ -This is a classic CRUD application, backed by a JDBC database. It demonstrates: +This is a classic Play CRUD application, backed by a JDBC database. It demonstrates: - Accessing a JDBC database, using Anorm. - Achieving, table pagination and CRUD forms. -- Integrating with a CSS framework (Twitter Bootstrap ). - -Twitter Bootstrap requires a different form layout to the default one that the Play form helper generates, so this application also provides an example of integrating a custom form input constructor. \ No newline at end of file +- Integrating with a CSS framework (Twitter Bootstrap ). \ No newline at end of file diff --git a/activator.properties b/activator.properties index ec1f1c319..aef2042a0 100644 --- a/activator.properties +++ b/activator.properties @@ -1,4 +1,4 @@ -name=computer-database -title=Computer Database -description=This is a classic CRUD application, backed by a JDBC database. It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). +name=computer-database-scala +title=Computer Database Scala +description=This is a classic Play CRUD application, backed by a JDBC database. It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). tags=scala,play,anorm,pagination \ No newline at end of file diff --git a/test/ApplicationSpec.scala b/test/ApplicationSpec.scala index 559895b89..5461cdc56 100644 --- a/test/ApplicationSpec.scala +++ b/test/ApplicationSpec.scala @@ -62,7 +62,7 @@ class ApplicationSpec extends Specification { status(badDateFormat) must equalTo(BAD_REQUEST) contentAsString(badDateFormat) must contain("""""") - contentAsString(badDateFormat) must contain("""""") + contentAsString(badDateFormat) must contain("""""") contentAsString(badDateFormat) must contain("""""") From 9cda85fde1d2dccc2d3791543fcab3f186199539 Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Tue, 15 Jul 2014 20:45:32 -0700 Subject: [PATCH 03/68] Removed secret --- conf/application.conf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/conf/application.conf b/conf/application.conf index 8a5ccdb7f..970d048f9 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -2,12 +2,6 @@ application.name=computer-database -# Secret key -# ~~~~~ -# The secret key is used to secure cryptographics functions. -# If you deploy your application to several instances be sure to use the same key! -application.secret="E27D^[_9W" - # Database configuration # ~~~~~ # You can declare as many datasources as you want. From 1bc9c3f729a117ca761a316f61e693f8307ac17e Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Thu, 31 Jul 2014 03:32:35 -0700 Subject: [PATCH 04/68] Upgrading to Play 2.3.2 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d6ac63ab4..80c99d642 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.1") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.2") // web plugins From ae48bd1bacad5753adc081340ccaa1ff13501fb7 Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Tue, 9 Dec 2014 19:37:32 -0800 Subject: [PATCH 05/68] Updated dependency versions --- build.sbt | 5 ++--- project/build.properties | 2 +- project/plugins.sbt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index f78286914..23ee733b4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,13 @@ name := "computer-database-scala" version := "0.0.1-SNAPSHOT" -scalaVersion := "2.11.1" +scalaVersion := "2.11.4" libraryDependencies ++= Seq( jdbc, anorm, "org.webjars" % "jquery" % "2.1.1", - "org.webjars" % "bootstrap" % "3.1.1", - "org.webjars" % "font-awesome" % "4.1.0" + "org.webjars" % "bootstrap" % "3.3.1" ) lazy val root = (project in file(".")).enablePlugins(PlayScala) \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index be6c454fb..748703f77 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.5 +sbt.version=0.13.7 diff --git a/project/plugins.sbt b/project/plugins.sbt index 80c99d642..70c1e8ad0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.7") // web plugins From 6af1b7a161755b9acb3aab5cc0710be422a4c026 Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Mon, 6 Jul 2015 03:49:33 -0700 Subject: [PATCH 06/68] Upgrade to Play 2.4 --- .gitignore | 2 +- app/controllers/Application.scala | 3 +++ app/views/createForm.scala.html | 2 +- app/views/editForm.scala.html | 2 +- app/views/list.scala.html | 2 +- app/views/main.scala.html | 2 +- build.sbt | 14 +++++++++----- conf/application.conf | 1 + project/build.properties | 2 +- project/plugins.sbt | 2 +- 10 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 0cf5555af..c88c3cb30 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ dist /RUNNING_PID .settings .target -.cache +.cache* bin .DS_Store activator-sbt-*-shim.sbt diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index d325c122f..bd34eb46c 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -5,6 +5,9 @@ import play.api.mvc._ import play.api.data._ import play.api.data.Forms._ +import play.api.Play.current +import play.api.i18n.Messages.Implicits._ + import anorm._ import views._ diff --git a/app/views/createForm.scala.html b/app/views/createForm.scala.html index 6142f29d0..2e0c48d27 100644 --- a/app/views/createForm.scala.html +++ b/app/views/createForm.scala.html @@ -1,4 +1,4 @@ -@(computerForm: Form[Computer], companies: Seq[(String, String)]) +@(computerForm: Form[Computer], companies: Seq[(String, String)])(implicit messages: Messages) @import helper._ diff --git a/app/views/editForm.scala.html b/app/views/editForm.scala.html index 591aaf171..fd976cf33 100644 --- a/app/views/editForm.scala.html +++ b/app/views/editForm.scala.html @@ -1,4 +1,4 @@ -@(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)]) +@(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)])(implicit messages: Messages) @import helper._ diff --git a/app/views/list.scala.html b/app/views/list.scala.html index b94506915..231635942 100644 --- a/app/views/list.scala.html +++ b/app/views/list.scala.html @@ -1,4 +1,4 @@ -@(currentPage: Page[(Computer, Option[Company])], currentOrderBy: Int, currentFilter: String)(implicit flash: play.api.mvc.Flash) +@(currentPage: Page[(Computer, Option[Company])], currentOrderBy: Int, currentFilter: String)(implicit flash: play.api.mvc.Flash, messages: Messages) @**************************************** * Helper generating navigation links * diff --git a/app/views/main.scala.html b/app/views/main.scala.html index d06e6d528..45910e8f1 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -1,4 +1,4 @@ -@(content: Html) +@(content: Html)(implicit messages: Messages) diff --git a/build.sbt b/build.sbt index 23ee733b4..f60226928 100644 --- a/build.sbt +++ b/build.sbt @@ -2,13 +2,17 @@ name := "computer-database-scala" version := "0.0.1-SNAPSHOT" -scalaVersion := "2.11.4" +scalaVersion := "2.11.7" + +resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" libraryDependencies ++= Seq( jdbc, - anorm, - "org.webjars" % "jquery" % "2.1.1", - "org.webjars" % "bootstrap" % "3.3.1" + evolutions, + specs2 % Test, + "com.typesafe.play" %% "anorm" % "2.4.0", + "org.webjars" % "jquery" % "2.1.4", + "org.webjars" % "bootstrap" % "3.3.5" ) -lazy val root = (project in file(".")).enablePlugins(PlayScala) \ No newline at end of file +lazy val root = (project in file(".")).enablePlugins(PlayScala) diff --git a/conf/application.conf b/conf/application.conf index 970d048f9..9d1d984b2 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1,6 +1,7 @@ # Configuration application.name=computer-database +play.i18n.langs = [ "en", "en-US"] # Database configuration # ~~~~~ diff --git a/project/build.properties b/project/build.properties index 748703f77..a6e117b61 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.7 +sbt.version=0.13.8 diff --git a/project/plugins.sbt b/project/plugins.sbt index 70c1e8ad0..3bab8c603 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.7") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.2") // web plugins From 3b374731b9b69b6b08fc087fe4de4d63fa07f51a Mon Sep 17 00:00:00 2001 From: Markus Jura Date: Mon, 6 Jul 2015 17:58:54 +0200 Subject: [PATCH 07/68] Code Review Play 2.4 upgrade --- conf/application.conf | 34 ++++++++++++---------------------- conf/logback.xml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 conf/logback.xml diff --git a/conf/application.conf b/conf/application.conf index 9d1d984b2..9edaad92c 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1,7 +1,15 @@ -# Configuration +# This is the main configuration file for the application. +# ~~~~~ + +# Secret key +# ~~~~~ +# The secret key is used to secure cryptographics functions. +# If you deploy your application to several instances be sure to use the same key! +play.crypto.secret="changeme" -application.name=computer-database -play.i18n.langs = [ "en", "en-US"] +# The application languages +# ~~~~~ +play.i18n.langs = ["en", "en-US"] # Database configuration # ~~~~~ @@ -12,22 +20,4 @@ db.default.url="jdbc:h2:mem:play" # Assets configuration # ~~~~~ -"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" - -# Logger -# ~~~~~ -# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory . - -# Root logger: -logger.root=ERROR - -# Logger used by the framework: -logger.play=INFO - -# Logger provided to your application: -logger.application=DEBUG - - - - - +"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" \ No newline at end of file diff --git a/conf/logback.xml b/conf/logback.xml new file mode 100644 index 000000000..d62e80859 --- /dev/null +++ b/conf/logback.xml @@ -0,0 +1,32 @@ + + + + + + logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + From 8b3cd0c51e2aa615e28deac35879413e815e6ca4 Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Tue, 28 Jul 2015 21:05:44 -0700 Subject: [PATCH 08/68] Update to Play 2.4 --- app/controllers/Application.scala | 2 +- build.sbt | 2 ++ conf/application.conf | 2 +- project/plugins.sbt | 16 ---------------- test/ApplicationSpec.scala | 16 +++++++++------- 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index bd34eb46c..7dfa454a6 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -16,7 +16,7 @@ import models._ /** * Manage a database of computers */ -object Application extends Controller { +class Application extends Controller { /** * This result directly redirect to the application home. diff --git a/build.sbt b/build.sbt index f60226928..a30f3251b 100644 --- a/build.sbt +++ b/build.sbt @@ -16,3 +16,5 @@ libraryDependencies ++= Seq( ) lazy val root = (project in file(".")).enablePlugins(PlayScala) + +routesGenerator := InjectedRoutesGenerator \ No newline at end of file diff --git a/conf/application.conf b/conf/application.conf index 9edaad92c..38bcd0c0b 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -9,7 +9,7 @@ play.crypto.secret="changeme" # The application languages # ~~~~~ -play.i18n.langs = ["en", "en-US"] +play.i18n.langs = ["en"] # Database configuration # ~~~~~ diff --git a/project/plugins.sbt b/project/plugins.sbt index 3bab8c603..a435a4e2d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,19 +2,3 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/release // The Play plugin addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.2") - -// web plugins - -addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0") - -addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.0") - -addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.0") - -addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.1") - -addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0") - -addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0") - -addSbtPlugin("com.typesafe.sbt" % "sbt-mocha" % "1.0.0") diff --git a/test/ApplicationSpec.scala b/test/ApplicationSpec.scala index 5461cdc56..70d0702bd 100644 --- a/test/ApplicationSpec.scala +++ b/test/ApplicationSpec.scala @@ -16,11 +16,13 @@ class ApplicationSpec extends Specification { // -- + val applicationController = new controllers.Application() + "Application" should { "redirect to the computer list on /" in { - val result = controllers.Application.index(FakeRequest()) + val result = applicationController.index(FakeRequest()) status(result) must equalTo(SEE_OTHER) redirectLocation(result) must beSome.which(_ == "/computers") @@ -30,7 +32,7 @@ class ApplicationSpec extends Specification { "list computers on the the first page" in { running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - val result = controllers.Application.list(0, 2, "")(FakeRequest()) + val result = applicationController.list(0, 2, "")(FakeRequest()) status(result) must equalTo(OK) contentAsString(result) must contain("574 computers found") @@ -41,7 +43,7 @@ class ApplicationSpec extends Specification { "filter computer by name" in { running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - val result = controllers.Application.list(0, 2, "Apple")(FakeRequest()) + val result = applicationController.list(0, 2, "Apple")(FakeRequest()) status(result) must equalTo(OK) contentAsString(result) must contain("13 computers found") @@ -52,11 +54,11 @@ class ApplicationSpec extends Specification { "create new computer" in { running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - val badResult = controllers.Application.save(FakeRequest()) + val badResult = applicationController.save(FakeRequest()) status(badResult) must equalTo(BAD_REQUEST) - val badDateFormat = controllers.Application.save( + val badDateFormat = applicationController.save( FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "badbadbad", "company" -> "1") ) @@ -66,7 +68,7 @@ class ApplicationSpec extends Specification { contentAsString(badDateFormat) must contain("""""") - val result = controllers.Application.save( + val result = applicationController.save( FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "2011-12-24", "company" -> "1") ) @@ -74,7 +76,7 @@ class ApplicationSpec extends Specification { redirectLocation(result) must beSome.which(_ == "/computers") flash(result).get("success") must beSome.which(_ == "Computer FooBar has been created") - val list = controllers.Application.list(0, 2, "FooBar")(FakeRequest()) + val list = applicationController.list(0, 2, "FooBar")(FakeRequest()) status(list) must equalTo(OK) contentAsString(list) must contain("One computer found") From ff56b93eda83b6b3ded764cee006c501a1e47539 Mon Sep 17 00:00:00 2001 From: Kip Sigman Date: Tue, 28 Jul 2015 21:08:03 -0700 Subject: [PATCH 09/68] Updated to ignore eclipse cache files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0cf5555af..c88c3cb30 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ dist /RUNNING_PID .settings .target -.cache +.cache* bin .DS_Store activator-sbt-*-shim.sbt From 248c06d7b46d642c97d8839229c4f7d6e5bc1015 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 13 Apr 2016 20:08:08 -0700 Subject: [PATCH 10/68] Upgrade to Play 2.5.x --- .travis.yml | 22 ++++ ...Application.scala => HomeController.scala} | 43 ++++---- app/models/CompanyService.scala | 38 +++++++ .../{Models.scala => ComputerService.scala} | 104 +++++++----------- app/views/createForm.scala.html | 19 ++-- app/views/editForm.scala.html | 18 ++- app/views/list.scala.html | 12 +- app/views/main.scala.html | 2 +- app/views/twitterBootstrapInput.scala.html | 12 -- build.sbt | 11 +- conf/logback.xml | 6 +- conf/routes | 14 +-- project/build.properties | 2 +- project/plugins.sbt | 2 +- test/ApplicationSpec.scala | 89 --------------- test/FunctionalSpec.scala | 70 ++++++++++++ test/IntegrationSpec.scala | 41 +++---- test/ModelSpec.scala | 47 ++++---- 18 files changed, 266 insertions(+), 286 deletions(-) create mode 100644 .travis.yml rename app/controllers/{Application.scala => HomeController.scala} (73%) create mode 100644 app/models/CompanyService.scala rename app/models/{Models.scala => ComputerService.scala} (68%) delete mode 100644 app/views/twitterBootstrapInput.scala.html delete mode 100644 test/ApplicationSpec.scala create mode 100644 test/FunctionalSpec.scala diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..05ebf3b6d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: scala +scala: + - 2.11.7 +sudo: false +# This is needed as long as the travis build environment is JDK 1.8.0 < u40 (at time of writing it is u31) +# Otherwise, FSpec fails due to deadlocks caused by CompletableFuture.thenCompose blocking in the trampoline +# executor. +addons: + apt: + packages: + - oracle-java8-installer +jdk: + - oraclejdk8 +cache: + directories: + - $HOME/.ivy2/cache +before_cache: + # Ensure changes to the cache aren't persisted + - rm -rf $HOME/.ivy2/cache/com.typesafe.play/* + - rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* + # Delete all ivydata files since ivy touches them on each build + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm diff --git a/app/controllers/Application.scala b/app/controllers/HomeController.scala similarity index 73% rename from app/controllers/Application.scala rename to app/controllers/HomeController.scala index 7dfa454a6..324c2f4ab 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/HomeController.scala @@ -1,27 +1,26 @@ package controllers -import play.api._ -import play.api.mvc._ -import play.api.data._ -import play.api.data.Forms._ +import javax.inject.Inject -import play.api.Play.current -import play.api.i18n.Messages.Implicits._ - -import anorm._ - -import views._ import models._ +import play.api.data.Forms._ +import play.api.data._ +import play.api.i18n._ +import play.api.mvc._ +import views._ /** * Manage a database of computers */ -class Application extends Controller { - +class HomeController @Inject() (computerService: ComputerService, + companyService: CompanyService, + val messagesApi: MessagesApi) + extends Controller with I18nSupport { + /** * This result directly redirect to the application home. */ - val Home = Redirect(routes.Application.list(0, 2, "")) + val Home = Redirect(routes.HomeController.list(0, 2, "")) /** * Describe the computer form (used in both edit and create screens). @@ -52,7 +51,7 @@ class Application extends Controller { */ def list(page: Int, orderBy: Int, filter: String) = Action { implicit request => Ok(html.list( - Computer.list(page = page, orderBy = orderBy, filter = ("%"+filter+"%")), + computerService.list(page = page, orderBy = orderBy, filter = ("%"+filter+"%")), orderBy, filter )) } @@ -63,8 +62,8 @@ class Application extends Controller { * @param id Id of the computer to edit */ def edit(id: Long) = Action { - Computer.findById(id).map { computer => - Ok(html.editForm(id, computerForm.fill(computer), Company.options)) + computerService.findById(id).map { computer => + Ok(html.editForm(id, computerForm.fill(computer), companyService.options)) }.getOrElse(NotFound) } @@ -75,9 +74,9 @@ class Application extends Controller { */ def update(id: Long) = Action { implicit request => computerForm.bindFromRequest.fold( - formWithErrors => BadRequest(html.editForm(id, formWithErrors, Company.options)), + formWithErrors => BadRequest(html.editForm(id, formWithErrors, companyService.options)), computer => { - Computer.update(id, computer) + computerService.update(id, computer) Home.flashing("success" -> "Computer %s has been updated".format(computer.name)) } ) @@ -87,7 +86,7 @@ class Application extends Controller { * Display the 'new computer form'. */ def create = Action { - Ok(html.createForm(computerForm, Company.options)) + Ok(html.createForm(computerForm, companyService.options)) } /** @@ -95,9 +94,9 @@ class Application extends Controller { */ def save = Action { implicit request => computerForm.bindFromRequest.fold( - formWithErrors => BadRequest(html.createForm(formWithErrors, Company.options)), + formWithErrors => BadRequest(html.createForm(formWithErrors, companyService.options)), computer => { - Computer.insert(computer) + computerService.insert(computer) Home.flashing("success" -> "Computer %s has been created".format(computer.name)) } ) @@ -107,7 +106,7 @@ class Application extends Controller { * Handle computer deletion. */ def delete(id: Long) = Action { - Computer.delete(id) + computerService.delete(id) Home.flashing("success" -> "Computer has been deleted") } diff --git a/app/models/CompanyService.scala b/app/models/CompanyService.scala new file mode 100644 index 000000000..6d6db7041 --- /dev/null +++ b/app/models/CompanyService.scala @@ -0,0 +1,38 @@ +package models + +import javax.inject.Inject + +import anorm.SqlParser._ +import anorm._ +import play.api.db.{DB, DBApi} + +case class Company(id: Option[Long] = None, name: String) + +@javax.inject.Singleton +class CompanyService @Inject() (dbapi: DBApi) { + + private val db = dbapi.database("default") + + /** + * Parse a Company from a ResultSet + */ + val simple = { + get[Option[Long]]("company.id") ~ + get[String]("company.name") map { + case id~name => Company(id, name) + } + } + + /** + * Construct the Map[String,String] needed to fill a select options set. + */ + def options: Seq[(String,String)] = db.withConnection { implicit connection => + SQL("select * from company order by name").as(simple *). + foldLeft[Seq[(String, String)]](Nil) { (cs, c) => + c.id.fold(cs) { id => cs :+ (id.toString -> c.name) } + } + } + +} + + diff --git a/app/models/Models.scala b/app/models/ComputerService.scala similarity index 68% rename from app/models/Models.scala rename to app/models/ComputerService.scala index 84c2ffd02..c44422b5d 100644 --- a/app/models/Models.scala +++ b/app/models/ComputerService.scala @@ -1,19 +1,14 @@ package models -import java.util.{Date} +import java.util.Date +import javax.inject.Inject -import play.api.db._ -import play.api.Play.current - -import anorm._ import anorm.SqlParser._ +import anorm._ +import play.api.db.DBApi -import scala.language.postfixOps - -case class Company(id: Option[Long] = None, name: String) case class Computer(id: Option[Long] = None, name: String, introduced: Option[Date], discontinued: Option[Date], companyId: Option[Long]) - /** * Helper for pagination. */ @@ -22,41 +17,45 @@ case class Page[A](items: Seq[A], page: Int, offset: Long, total: Long) { lazy val next = Option(page + 1).filter(_ => (offset + items.size) < total) } -object Computer { - + +@javax.inject.Singleton +class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { + + private val db = dbapi.database("default") + // -- Parsers - + /** * Parse a Computer from a ResultSet */ val simple = { get[Option[Long]]("computer.id") ~ - get[String]("computer.name") ~ - get[Option[Date]]("computer.introduced") ~ - get[Option[Date]]("computer.discontinued") ~ - get[Option[Long]]("computer.company_id") map { + get[String]("computer.name") ~ + get[Option[Date]]("computer.introduced") ~ + get[Option[Date]]("computer.discontinued") ~ + get[Option[Long]]("computer.company_id") map { case id~name~introduced~discontinued~companyId => Computer(id, name, introduced, discontinued, companyId) } } - + /** * Parse a (Computer,Company) from a ResultSet */ - val withCompany = Computer.simple ~ (Company.simple ?) map { + val withCompany = simple ~ (companyService.simple ?) map { case computer~company => (computer,company) } - + // -- Queries - + /** * Retrieve a computer from the id. */ def findById(id: Long): Option[Computer] = { - DB.withConnection { implicit connection => - SQL("select * from computer where id = {id}").on('id -> id).as(Computer.simple.singleOpt) + db.withConnection { implicit connection => + SQL("select * from computer where id = {id}").on('id -> id).as(simple.singleOpt) } } - + /** * Return a page of (Computer,Company). * @@ -66,29 +65,29 @@ object Computer { * @param filter Filter applied on the name column */ def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Page[(Computer, Option[Company])] = { - + val offest = pageSize * page - - DB.withConnection { implicit connection => - + + db.withConnection { implicit connection => + val computers = SQL( """ - select * from computer + select * from computer left join company on computer.company_id = company.id where computer.name like {filter} order by {orderBy} nulls last limit {pageSize} offset {offset} """ ).on( - 'pageSize -> pageSize, + 'pageSize -> pageSize, 'offset -> offest, 'filter -> filter, 'orderBy -> orderBy - ).as(Computer.withCompany *) + ).as(withCompany *) val totalRows = SQL( """ - select count(*) from computer + select count(*) from computer left join company on computer.company_id = company.id where computer.name like {filter} """ @@ -97,11 +96,11 @@ object Computer { ).as(scalar[Long].single) Page(computers, page, offest, totalRows) - + } - + } - + /** * Update a computer. * @@ -109,7 +108,7 @@ object Computer { * @param computer The computer values. */ def update(id: Long, computer: Computer) = { - DB.withConnection { implicit connection => + db.withConnection { implicit connection => SQL( """ update computer @@ -125,18 +124,18 @@ object Computer { ).executeUpdate() } } - + /** * Insert a new computer. * * @param computer The computer values. */ def insert(computer: Computer) = { - DB.withConnection { implicit connection => + db.withConnection { implicit connection => SQL( """ insert into computer values ( - (select next value for computer_seq), + (select next value for computer_seq), {name}, {introduced}, {discontinued}, {company_id} ) """ @@ -148,41 +147,16 @@ object Computer { ).executeUpdate() } } - + /** * Delete a computer. * * @param id Id of the computer to delete. */ def delete(id: Long) = { - DB.withConnection { implicit connection => + db.withConnection { implicit connection => SQL("delete from computer where id = {id}").on('id -> id).executeUpdate() } } - -} -object Company { - - /** - * Parse a Company from a ResultSet - */ - val simple = { - get[Option[Long]]("company.id") ~ - get[String]("company.name") map { - case id~name => Company(id, name) - } - } - - /** - * Construct the Map[String,String] needed to fill a select options set. - */ - def options: Seq[(String,String)] = DB.withConnection { implicit connection => - SQL("select * from company order by name").as(Company.simple *). - foldLeft[Seq[(String, String)]](Nil) { (cs, c) => - c.id.fold(cs) { id => cs :+ (id.toString -> c.name) } - } - } - } - diff --git a/app/views/createForm.scala.html b/app/views/createForm.scala.html index 2e0c48d27..73f3a7749 100644 --- a/app/views/createForm.scala.html +++ b/app/views/createForm.scala.html @@ -1,34 +1,29 @@ @(computerForm: Form[Computer], companies: Seq[(String, String)])(implicit messages: Messages) -@import helper._ - -@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.f) } +@import b3.vertical.fieldConstructor @main {

Add a computer

- @form(routes.Application.save()) { + @b3.form(routes.HomeController.save()) {
- - @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") - @inputDate(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") - @inputDate(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + @b3.text(computerForm("name"), '_label -> "Computer name", '_help -> "") + @b3.date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @b3.date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") - @select( + @b3.select( computerForm("company"), companies, '_label -> "Company", '_default -> "-- Choose a company --", '_showConstraints -> false ) - -
or - Cancel + Cancel
} diff --git a/app/views/editForm.scala.html b/app/views/editForm.scala.html index fd976cf33..47f2987ee 100644 --- a/app/views/editForm.scala.html +++ b/app/views/editForm.scala.html @@ -1,22 +1,20 @@ @(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)])(implicit messages: Messages) -@import helper._ - -@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.f) } +@import b3.vertical.fieldConstructor @main {

Edit computer

- @form(routes.Application.update(id)) { + @b3.form(routes.HomeController.update(id)) {
- @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") - @inputDate(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") - @inputDate(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + @b3.text(computerForm("name"), '_label -> "Computer name", '_help -> "") + @b3.date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @b3.date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") - @select( + @b3.select( computerForm("company"), companies, '_label -> "Company", '_default -> "-- Choose a company --", @@ -27,12 +25,12 @@

Edit computer

or - Cancel + Cancel
} - @form(routes.Application.delete(id), 'class -> "topRight") { + @b3.form(routes.HomeController.delete(id), 'class -> "topRight") { } diff --git a/app/views/list.scala.html b/app/views/list.scala.html index 231635942..7710b1388 100644 --- a/app/views/list.scala.html +++ b/app/views/list.scala.html @@ -1,10 +1,14 @@ @(currentPage: Page[(Computer, Option[Company])], currentOrderBy: Int, currentFilter: String)(implicit flash: play.api.mvc.Flash, messages: Messages) +@import helper._ + +@import b3.vertical.fieldConstructor + @**************************************** * Helper generating navigation links * ****************************************@ @link(newPage: Int, newOrderBy: Option[Int] = None) = @{ - routes.Application.list(newPage, newOrderBy.map { orderBy => + routes.HomeController.list(newPage, newOrderBy.map { orderBy => if(orderBy == scala.math.abs(currentOrderBy)) -currentOrderBy else orderBy }.getOrElse(currentOrderBy), currentFilter) @@ -31,12 +35,12 @@

@Messages("computers.list.title", currentPage.total)

- @helper.form(action=routes.Application.list()) { + @b3.form(action=routes.HomeController.list()) { } - Add a new computer + Add a new computer
@@ -56,7 +60,7 @@

@Messages("computers.list.title", currentPage.total)

@computers.map { case (computer, company) => { - @computer.name + @computer.name @computer.introduced.map(_.format("dd MMM yyyy")).getOrElse { - } diff --git a/app/views/main.scala.html b/app/views/main.scala.html index 45910e8f1..5f3e1536f 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -22,7 +22,7 @@

- + Play sample application — Computer database

diff --git a/app/views/twitterBootstrapInput.scala.html b/app/views/twitterBootstrapInput.scala.html deleted file mode 100644 index 4c27a78d8..000000000 --- a/app/views/twitterBootstrapInput.scala.html +++ /dev/null @@ -1,12 +0,0 @@ -@(elements: helper.FieldElements) - -@************************************************** -* Generate input according twitter bootsrap rules * -**************************************************@ -
- -
- @elements.input - @elements.infos.mkString(", ") -
-
diff --git a/build.sbt b/build.sbt index a30f3251b..389ff5cb7 100644 --- a/build.sbt +++ b/build.sbt @@ -9,12 +9,9 @@ resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" libraryDependencies ++= Seq( jdbc, evolutions, - specs2 % Test, - "com.typesafe.play" %% "anorm" % "2.4.0", - "org.webjars" % "jquery" % "2.1.4", - "org.webjars" % "bootstrap" % "3.3.5" -) + "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3", + "com.typesafe.play" %% "anorm" % "2.5.0", + "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.0" % "test" +) lazy val root = (project in file(".")).enablePlugins(PlayScala) - -routesGenerator := InjectedRoutesGenerator \ No newline at end of file diff --git a/conf/logback.xml b/conf/logback.xml index d62e80859..3df42b7a7 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -1,6 +1,6 @@ - + logs/application.log @@ -22,7 +22,9 @@ - + + + diff --git a/conf/routes b/conf/routes index 57deb0736..f54d67406 100644 --- a/conf/routes +++ b/conf/routes @@ -3,21 +3,21 @@ # ~~~~ # Default path will just redirect to the computer list -GET / controllers.Application.index +GET / controllers.HomeController.index # Computers list (look at the default values for pagination parameters) -GET /computers controllers.Application.list(p:Int ?= 0, s:Int ?= 2, f ?= "") +GET /computers controllers.HomeController.list(p:Int ?= 0, s:Int ?= 2, f ?= "") # Add computer -GET /computers/new controllers.Application.create -POST /computers controllers.Application.save +GET /computers/new controllers.HomeController.create +POST /computers controllers.HomeController.save # Edit existing computer -GET /computers/:id controllers.Application.edit(id:Long) -POST /computers/:id controllers.Application.update(id:Long) +GET /computers/:id controllers.HomeController.edit(id:Long) +POST /computers/:id controllers.HomeController.update(id:Long) # Delete a computer -POST /computers/:id/delete controllers.Application.delete(id:Long) +POST /computers/:id/delete controllers.HomeController.delete(id:Long) # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.at(path="/public", file) diff --git a/project/build.properties b/project/build.properties index a6e117b61..43b8278c6 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=0.13.11 diff --git a/project/plugins.sbt b/project/plugins.sbt index a435a4e2d..1a7f1eaba 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.1") diff --git a/test/ApplicationSpec.scala b/test/ApplicationSpec.scala deleted file mode 100644 index 70d0702bd..000000000 --- a/test/ApplicationSpec.scala +++ /dev/null @@ -1,89 +0,0 @@ -import org.specs2.mutable._ -import org.specs2.runner._ -import org.junit.runner._ - -import play.api.test._ -import play.api.test.Helpers._ - -@RunWith(classOf[JUnitRunner]) -class ApplicationSpec extends Specification { - - import models._ - - // -- Date helpers - - def dateIs(date: java.util.Date, str: String) = new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str - - // -- - - val applicationController = new controllers.Application() - - "Application" should { - - "redirect to the computer list on /" in { - - val result = applicationController.index(FakeRequest()) - - status(result) must equalTo(SEE_OTHER) - redirectLocation(result) must beSome.which(_ == "/computers") - - } - - "list computers on the the first page" in { - running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - - val result = applicationController.list(0, 2, "")(FakeRequest()) - - status(result) must equalTo(OK) - contentAsString(result) must contain("574 computers found") - - } - } - - "filter computer by name" in { - running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - - val result = applicationController.list(0, 2, "Apple")(FakeRequest()) - - status(result) must equalTo(OK) - contentAsString(result) must contain("13 computers found") - - } - } - - "create new computer" in { - running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - - val badResult = applicationController.save(FakeRequest()) - - status(badResult) must equalTo(BAD_REQUEST) - - val badDateFormat = applicationController.save( - FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "badbadbad", "company" -> "1") - ) - - status(badDateFormat) must equalTo(BAD_REQUEST) - contentAsString(badDateFormat) must contain("""""") - contentAsString(badDateFormat) must contain("""""") - contentAsString(badDateFormat) must contain("""""") - - - val result = applicationController.save( - FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "2011-12-24", "company" -> "1") - ) - - status(result) must equalTo(SEE_OTHER) - redirectLocation(result) must beSome.which(_ == "/computers") - flash(result).get("success") must beSome.which(_ == "Computer FooBar has been created") - - val list = applicationController.list(0, 2, "FooBar")(FakeRequest()) - - status(list) must equalTo(OK) - contentAsString(list) must contain("One computer found") - - } - } - - } - -} \ No newline at end of file diff --git a/test/FunctionalSpec.scala b/test/FunctionalSpec.scala new file mode 100644 index 000000000..c1c24b536 --- /dev/null +++ b/test/FunctionalSpec.scala @@ -0,0 +1,70 @@ + +import controllers.HomeController +import org.scalatest.concurrent.ScalaFutures +import play.api.test._ +import play.api.test.Helpers._ +import org.scalatestplus.play._ + +class FunctionalSpec extends PlaySpec with OneAppPerSuite with ScalaFutures { + + def dateIs(date: java.util.Date, str: String) = { + new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str + } + + def homeController = app.injector.instanceOf(classOf[HomeController]) + + "HomeController" should { + + "redirect to the computer list on /" in { + val result = homeController.index(FakeRequest()) + + status(result) must equal(SEE_OTHER) + redirectLocation(result) mustBe Some("/computers") + } + + "list computers on the the first page" in { + val result = homeController.list(0, 2, "")(FakeRequest()) + + status(result) must equal(OK) + contentAsString(result) must include("574 computers found") + } + + "filter computer by name" in { + val result = homeController.list(0, 2, "Apple")(FakeRequest()) + + status(result) must equal(OK) + contentAsString(result) must include("13 computers found") + } + + //running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { + + "create new computer" in { + val badResult = homeController.save(FakeRequest()) + + status(badResult) must equal(BAD_REQUEST) + + val badDateFormat = homeController.save( + FakeRequest().withFormUrlEncodedBody("name" -> "FooBar", "introduced" -> "badbadbad", "company" -> "1") + ) + + status(badDateFormat) must equal(BAD_REQUEST) + contentAsString(badDateFormat) must include("""""") + contentAsString(badDateFormat) must include(""" "FooBar", "introduced" -> "2011-12-24", "company" -> "1") + ) + + status(result) must equal(SEE_OTHER) + redirectLocation(result) mustBe Some("/computers") + flash(result).get("success") mustBe Some("Computer FooBar has been created") + + val list = homeController.list(0, 2, "FooBar")(FakeRequest()) + + status(list) must equal(OK) + contentAsString(list) must include("One computer found") + } + } +} \ No newline at end of file diff --git a/test/IntegrationSpec.scala b/test/IntegrationSpec.scala index 562ee68fb..c3bf78045 100644 --- a/test/IntegrationSpec.scala +++ b/test/IntegrationSpec.scala @@ -1,14 +1,11 @@ -import org.specs2.mutable._ -import org.specs2.runner._ -import org.junit.runner._ - import play.api.test._ import play.api.test.Helpers._ - import org.fluentlenium.core.filter.FilterConstructor._ +import org.scalatestplus.play.PlaySpec -@RunWith(classOf[JUnitRunner]) -class IntegrationSpec extends Specification { +import org.scalatestplus.play._ + +class IntegrationSpec extends PlaySpec { "Application" should { @@ -16,33 +13,27 @@ class IntegrationSpec extends Specification { running(TestServer(3333), HTMLUNIT) { browser => browser.goTo("http://localhost:3333/") - browser.$("header h1").first.getText must equalTo("Play sample application — Computer database") - browser.$("section h1").first.getText must equalTo("574 computers found") + browser.$("header h1").first.getText must equal("Play sample application — Computer database") + browser.$("section h1").first.getText must equal("574 computers found") - browser.$("#pagination li.current").first.getText must equalTo("Displaying 1 to 10 of 574") + browser.$("#pagination li.current").first.getText must equal("Displaying 1 to 10 of 574") browser.$("#pagination li.next a").click() - browser.$("#pagination li.current").first.getText must equalTo("Displaying 11 to 20 of 574") + browser.$("#pagination li.current").first.getText must equal("Displaying 11 to 20 of 574") browser.$("#searchbox").text("Apple") browser.$("#searchsubmit").click() - browser.$("section h1").first.getText must equalTo("13 computers found") + browser.$("section h1").first.getText must equal("13 computers found") browser.$("a", withText("Apple II")).click() - browser.$("section h1").first.getText must equalTo("Edit computer") - - browser.$("#discontinued").text("xxx") - browser.$("input.primary").click() + browser.$("section h1").first.getText must equal("Edit computer") - browser.$("div.error").size must equalTo(1) - browser.$("div.error label").first.getText must equalTo("Discontinued date") - browser.$("#discontinued").text("") browser.$("input.primary").click() - - browser.$("section h1").first.getText must equalTo("574 computers found") - browser.$(".alert-message").first.getText must equalTo("Done! Computer Apple II has been updated") + + browser.$("section h1").first.getText must equal("574 computers found") + browser.$(".alert-message").first.getText must equal("Done! Computer Apple II has been updated") browser.$("#searchbox").text("Apple") browser.$("#searchsubmit").click() @@ -50,13 +41,13 @@ class IntegrationSpec extends Specification { browser.$("a", withText("Apple II")).click() browser.$("input.danger").click() - browser.$("section h1").first.getText must equalTo("573 computers found") - browser.$(".alert-message").first.getText must equalTo("Done! Computer has been deleted") + browser.$("section h1").first.getText must equal("573 computers found") + browser.$(".alert-message").first.getText must equal("Done! Computer has been deleted") browser.$("#searchbox").text("Apple") browser.$("#searchsubmit").click() - browser.$("section h1").first.getText must equalTo("12 computers found") + browser.$("section h1").first.getText must equal("12 computers found") } } diff --git a/test/ModelSpec.scala b/test/ModelSpec.scala index e0656fc69..762d91cb1 100644 --- a/test/ModelSpec.scala +++ b/test/ModelSpec.scala @@ -1,13 +1,14 @@ -import org.specs2.mutable._ -import org.specs2.runner._ -import org.junit.runner._ - +import models.ComputerService import play.api.test._ import play.api.test.Helpers._ +import org.scalatestplus.play._ + +import org.scalatest.OptionValues._ + +class ModelSpec extends PlaySpec with OneAppPerSuite { + + var computerService: ComputerService = app.injector.instanceOf(classOf[ComputerService]) -@RunWith(classOf[JUnitRunner]) -class ModelSpec extends Specification { - import models._ // -- Date helpers @@ -19,38 +20,28 @@ class ModelSpec extends Specification { "Computer model" should { "be retrieved by id" in { - running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - - val Some(macintosh) = Computer.findById(21) + val macintosh = computerService.findById(21).get - macintosh.name must equalTo("Macintosh") - macintosh.introduced must beSome.which(dateIs(_, "1984-01-24")) - - } + macintosh.name must equal("Macintosh") + macintosh.introduced.value must matchPattern { case date:java.util.Date if dateIs(date, "1984-01-24") => } } "be listed along its companies" in { - running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - - val computers = Computer.list() - computers.total must equalTo(574) - computers.items must have length(10) + val computers = computerService.list() - } + computers.total must equal(574) + computers.items must have length(10) } "be updated if needed" in { - running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { - - Computer.update(21, Computer(name="The Macintosh", introduced=None, discontinued=None, companyId=Some(1))) - - val Some(macintosh) = Computer.findById(21) + + computerService.update(21, Computer(name="The Macintosh", introduced=None, discontinued=None, companyId=Some(1))) - macintosh.name must equalTo("The Macintosh") - macintosh.introduced must beNone + val macintosh = computerService.findById(21).get - } + macintosh.name must equal("The Macintosh") + macintosh.introduced mustBe None } } From d4d98590d55283b0334dc9ff65a8270c83eb2caa Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Fri, 15 Apr 2016 14:47:52 -0700 Subject: [PATCH 11/68] Clean up and formatting --- app/models/CompanyService.scala | 3 ++- app/models/ComputerService.scala | 9 +++++++-- test/ModelSpec.scala | 17 ++++++++++------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/models/CompanyService.scala b/app/models/CompanyService.scala index 6d6db7041..73cc19d6b 100644 --- a/app/models/CompanyService.scala +++ b/app/models/CompanyService.scala @@ -4,7 +4,8 @@ import javax.inject.Inject import anorm.SqlParser._ import anorm._ -import play.api.db.{DB, DBApi} + +import play.api.db.DBApi case class Company(id: Option[Long] = None, name: String) diff --git a/app/models/ComputerService.scala b/app/models/ComputerService.scala index c44422b5d..1c6684d09 100644 --- a/app/models/ComputerService.scala +++ b/app/models/ComputerService.scala @@ -7,7 +7,11 @@ import anorm.SqlParser._ import anorm._ import play.api.db.DBApi -case class Computer(id: Option[Long] = None, name: String, introduced: Option[Date], discontinued: Option[Date], companyId: Option[Long]) +case class Computer(id: Option[Long] = None, + name: String, + introduced: Option[Date], + discontinued: Option[Date], + companyId: Option[Long]) /** * Helper for pagination. @@ -34,7 +38,8 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { get[Option[Date]]("computer.introduced") ~ get[Option[Date]]("computer.discontinued") ~ get[Option[Long]]("computer.company_id") map { - case id~name~introduced~discontinued~companyId => Computer(id, name, introduced, discontinued, companyId) + case id~name~introduced~discontinued~companyId => + Computer(id, name, introduced, discontinued, companyId) } } diff --git a/test/ModelSpec.scala b/test/ModelSpec.scala index 762d91cb1..37797d913 100644 --- a/test/ModelSpec.scala +++ b/test/ModelSpec.scala @@ -1,10 +1,6 @@ import models.ComputerService -import play.api.test._ -import play.api.test.Helpers._ import org.scalatestplus.play._ -import org.scalatest.OptionValues._ - class ModelSpec extends PlaySpec with OneAppPerSuite { var computerService: ComputerService = app.injector.instanceOf(classOf[ComputerService]) @@ -13,7 +9,9 @@ class ModelSpec extends PlaySpec with OneAppPerSuite { // -- Date helpers - def dateIs(date: java.util.Date, str: String) = new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str + def dateIs(date: java.util.Date, str: String) = { + new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str + } // -- @@ -23,7 +21,9 @@ class ModelSpec extends PlaySpec with OneAppPerSuite { val macintosh = computerService.findById(21).get macintosh.name must equal("Macintosh") - macintosh.introduced.value must matchPattern { case date:java.util.Date if dateIs(date, "1984-01-24") => } + macintosh.introduced.value must matchPattern { + case date:java.util.Date if dateIs(date, "1984-01-24") => + } } "be listed along its companies" in { @@ -36,7 +36,10 @@ class ModelSpec extends PlaySpec with OneAppPerSuite { "be updated if needed" in { - computerService.update(21, Computer(name="The Macintosh", introduced=None, discontinued=None, companyId=Some(1))) + computerService.update(21, Computer(name="The Macintosh", + introduced=None, + discontinued=None, + companyId=Some(1))) val macintosh = computerService.findById(21).get From 7bac931ba84402212658fc4e1c9eabc920da3834 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Fri, 15 Apr 2016 15:26:26 -0700 Subject: [PATCH 12/68] Update the README --- README | 5 ----- README.md | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index fae03678b..000000000 --- a/README +++ /dev/null @@ -1,5 +0,0 @@ -This is a classic Play CRUD application, backed by a JDBC database. It demonstrates: - -- Accessing a JDBC database, using Anorm. -- Achieving, table pagination and CRUD forms. -- Integrating with a CSS framework (Twitter Bootstrap ). \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..6675f6281 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Computer-Database-Scala + +This is an example Play application that uses Scala on the front end, and communicates with an in memory database using Anorm. + +The Github location for this project is: + +[https://github.com/typesafehub/activator-computer-database-scala](https://github.com/typesafehub/activator-computer-database-scala) + +## Play + +Play documentation is here: + +[https://playframework.com/documentation/latest/Home](https://playframework.com/documentation/latest/Home) + +## Anorm + +Anorm is a Scala ORM library that uses SQL: + +[https://www.playframework.com/documentation/2.5.x/ScalaAnorm](https://www.playframework.com/documentation/2.5.x/ScalaAnorm) + +and + +[https://cchantep.github.io/anorm/](https://cchantep.github.io/anorm/) + +## Play Bootstrap + +The Play HTML templates use the Play Bootstrap library: + +[https://github.com/adrianhurt/play-bootstrap](https://github.com/adrianhurt/play-bootstrap) + +library to integrate Play with Bootstrap, the popular CSS Framework. + From e99746c23d666cb5fef2a3c83258667fa18f37ec Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 12 May 2016 20:44:39 -0400 Subject: [PATCH 13/68] Change name to play-anorm (#9) Change the project name to play-anorm to match the other example projects. --- README.md | 2 +- activator.properties | 8 ++++---- build.sbt | 8 ++++---- project/plugins.sbt | 2 +- tutorial/index.html | 10 +++++++--- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6675f6281..de010622a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Computer-Database-Scala +# play-anorm This is an example Play application that uses Scala on the front end, and communicates with an in memory database using Anorm. diff --git a/activator.properties b/activator.properties index aef2042a0..71b0ddd98 100644 --- a/activator.properties +++ b/activator.properties @@ -1,4 +1,4 @@ -name=computer-database-scala -title=Computer Database Scala -description=This is a classic Play CRUD application, backed by a JDBC database. It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). -tags=scala,play,anorm,pagination \ No newline at end of file +name=play-anorm +title=Play Anorm +description=This is a Play CRUD application in Scala using Anorm and Play-Bootstrap. +tags=scala,play,anorm,pagination,bootstrap \ No newline at end of file diff --git a/build.sbt b/build.sbt index 389ff5cb7..d3fa16bcb 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ -name := "computer-database-scala" +name := "play-anorm" version := "0.0.1-SNAPSHOT" -scalaVersion := "2.11.7" +scalaVersion := "2.11.8" resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" @@ -10,8 +10,8 @@ libraryDependencies ++= Seq( jdbc, evolutions, "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3", - "com.typesafe.play" %% "anorm" % "2.5.0", - "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.0" % "test" + "com.typesafe.play" %% "anorm" % "2.5.1", + "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" ) lazy val root = (project in file(".")).enablePlugins(PlayScala) diff --git a/project/plugins.sbt b/project/plugins.sbt index 1a7f1eaba..d4a6b46fe 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.1") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.3") diff --git a/tutorial/index.html b/tutorial/index.html index 3d3fb75ad..e017a34a1 100644 --- a/tutorial/index.html +++ b/tutorial/index.html @@ -1,13 +1,17 @@ - Computer Database + play-anorm
-

Computer Database

+

play-anorm

- This is a classic CRUD application, backed by a JDBC database. It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). + This is a classic CRUD application, backed by a JDBC database. +

+ +

+ It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap).

From 4232a88c95caf8b27d3b712961cc6fc260e84dff Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 6 Oct 2016 00:17:27 -0500 Subject: [PATCH 14/68] Fix for integration tests by revert to Anorm 2.5.0 (#11) --- .travis.yml | 2 +- build.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 05ebf3b6d..313c49288 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala scala: - - 2.11.7 + - 2.11.8 sudo: false # This is needed as long as the travis build environment is JDK 1.8.0 < u40 (at time of writing it is u31) # Otherwise, FSpec fails due to deadlocks caused by CompletableFuture.thenCompose blocking in the trampoline diff --git a/build.sbt b/build.sbt index d3fa16bcb..78b418c4a 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ libraryDependencies ++= Seq( jdbc, evolutions, "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3", - "com.typesafe.play" %% "anorm" % "2.5.1", + "com.typesafe.play" %% "anorm" % "2.5.0", "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" ) From 04f4355afeae363df28547d9be8513faa348fef9 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 6 Oct 2016 00:47:20 -0500 Subject: [PATCH 15/68] add slack (#12) --- .travis.yml | 25 +++++++++---------------- build.sbt | 15 +++++++-------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 313c49288..1347ae7ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,15 @@ language: scala scala: - - 2.11.8 -sudo: false -# This is needed as long as the travis build environment is JDK 1.8.0 < u40 (at time of writing it is u31) -# Otherwise, FSpec fails due to deadlocks caused by CompletableFuture.thenCompose blocking in the trampoline -# executor. -addons: - apt: - packages: - - oracle-java8-installer +- 2.11.8 jdk: - - oraclejdk8 +- oraclejdk8 cache: directories: - - $HOME/.ivy2/cache + - "$HOME/.ivy2/cache" before_cache: - # Ensure changes to the cache aren't persisted - - rm -rf $HOME/.ivy2/cache/com.typesafe.play/* - - rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* - # Delete all ivydata files since ivy touches them on each build - - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm +- rm -rf $HOME/.ivy2/cache/com.typesafe.play/* +- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* +- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm +notifications: + slack: + secure: a738ADtNq3eN26B6LCcd+NY83s+PQjQagL0S9HhBFvCpvzkQ/CwAK9l5keWayM2S4IUBOmsS/uMuKodmP7QoPN3XxUGgsZNTMjHT1FAa/rc6N2MKF5C20fBII1/cJOuXBZ5x+2EW+odch3+0pafMCXI/RZZLiHELD2Jx6JGS0lQ= diff --git a/build.sbt b/build.sbt index 78b418c4a..626170c3a 100644 --- a/build.sbt +++ b/build.sbt @@ -6,12 +6,11 @@ scalaVersion := "2.11.8" resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" -libraryDependencies ++= Seq( - jdbc, - evolutions, - "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3", - "com.typesafe.play" %% "anorm" % "2.5.0", - "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" -) - lazy val root = (project in file(".")).enablePlugins(PlayScala) + +libraryDependencies += jdbc +libraryDependencies += evolutions +libraryDependencies += "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3" +libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.0" +libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" + From 60f1b1beb5e7a34330c8812bd31789c3d00abe82 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 6 Oct 2016 00:50:51 -0500 Subject: [PATCH 16/68] snapshot --- build.sbt | 1 + project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 626170c3a..e6eb4b1b7 100644 --- a/build.sbt +++ b/build.sbt @@ -8,6 +8,7 @@ resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" lazy val root = (project in file(".")).enablePlugins(PlayScala) +libraryDependencies += guice libraryDependencies += jdbc libraryDependencies += evolutions libraryDependencies += "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3" diff --git a/project/plugins.sbt b/project/plugins.sbt index d4a6b46fe..f99d2b516 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.3") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-SNAPSHOT") From 9a5912ca81621b9fd9f99f0d229cd3d41115f4db Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sat, 18 Feb 2017 18:30:09 -0800 Subject: [PATCH 17/68] Upgrade to 2.6.x (#24) --- README.md | 13 ------------- app/controllers/HomeController.scala | 10 ++++++---- app/views/createForm.scala.html | 12 ++++++------ app/views/editForm.scala.html | 14 +++++++------- app/views/helper/date.scala.html | 7 +++++++ app/views/list.scala.html | 5 +---- build.sbt | 7 ++++--- project/build.properties | 2 +- project/plugins.sbt | 2 +- 9 files changed, 33 insertions(+), 39 deletions(-) create mode 100644 app/views/helper/date.scala.html diff --git a/README.md b/README.md index de010622a..157de82e4 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,6 @@ This is an example Play application that uses Scala on the front end, and communicates with an in memory database using Anorm. -The Github location for this project is: - -[https://github.com/typesafehub/activator-computer-database-scala](https://github.com/typesafehub/activator-computer-database-scala) - ## Play Play documentation is here: @@ -21,12 +17,3 @@ Anorm is a Scala ORM library that uses SQL: and [https://cchantep.github.io/anorm/](https://cchantep.github.io/anorm/) - -## Play Bootstrap - -The Play HTML templates use the Play Bootstrap library: - -[https://github.com/adrianhurt/play-bootstrap](https://github.com/adrianhurt/play-bootstrap) - -library to integrate Play with Bootstrap, the popular CSS Framework. - diff --git a/app/controllers/HomeController.scala b/app/controllers/HomeController.scala index 324c2f4ab..8bfc84adc 100644 --- a/app/controllers/HomeController.scala +++ b/app/controllers/HomeController.scala @@ -14,8 +14,10 @@ import views._ */ class HomeController @Inject() (computerService: ComputerService, companyService: CompanyService, - val messagesApi: MessagesApi) - extends Controller with I18nSupport { + cc: ControllerComponents) + extends AbstractController(cc) with I18nSupport { + + implicit def request2flash(implicit request: RequestHeader): Flash = request.flash /** * This result directly redirect to the application home. @@ -61,7 +63,7 @@ class HomeController @Inject() (computerService: ComputerService, * * @param id Id of the computer to edit */ - def edit(id: Long) = Action { + def edit(id: Long) = Action { implicit request => computerService.findById(id).map { computer => Ok(html.editForm(id, computerForm.fill(computer), companyService.options)) }.getOrElse(NotFound) @@ -85,7 +87,7 @@ class HomeController @Inject() (computerService: ComputerService, /** * Display the 'new computer form'. */ - def create = Action { + def create = Action { implicit request => Ok(html.createForm(computerForm, companyService.options)) } diff --git a/app/views/createForm.scala.html b/app/views/createForm.scala.html index 73f3a7749..7e1e934fe 100644 --- a/app/views/createForm.scala.html +++ b/app/views/createForm.scala.html @@ -1,19 +1,19 @@ @(computerForm: Form[Computer], companies: Seq[(String, String)])(implicit messages: Messages) -@import b3.vertical.fieldConstructor +@import views.html.helper._ @main {

Add a computer

- @b3.form(routes.HomeController.save()) { + @form(routes.HomeController.save()) {
- @b3.text(computerForm("name"), '_label -> "Computer name", '_help -> "") - @b3.date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") - @b3.date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") + @date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") - @b3.select( + @select( computerForm("company"), companies, '_label -> "Company", '_default -> "-- Choose a company --", diff --git a/app/views/editForm.scala.html b/app/views/editForm.scala.html index 47f2987ee..e51a3ca16 100644 --- a/app/views/editForm.scala.html +++ b/app/views/editForm.scala.html @@ -1,20 +1,20 @@ @(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)])(implicit messages: Messages) -@import b3.vertical.fieldConstructor +@import views.html.helper._ @main {

Edit computer

- @b3.form(routes.HomeController.update(id)) { + @form(routes.HomeController.update(id)) {
- @b3.text(computerForm("name"), '_label -> "Computer name", '_help -> "") - @b3.date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") - @b3.date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") + @date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") - @b3.select( + @select( computerForm("company"), companies, '_label -> "Company", '_default -> "-- Choose a company --", @@ -30,7 +30,7 @@

Edit computer

} - @b3.form(routes.HomeController.delete(id), 'class -> "topRight") { + @form(routes.HomeController.delete(id), 'class -> "topRight") { } diff --git a/app/views/helper/date.scala.html b/app/views/helper/date.scala.html new file mode 100644 index 000000000..6b55780f9 --- /dev/null +++ b/app/views/helper/date.scala.html @@ -0,0 +1,7 @@ +@(field: play.api.data.Field, args: (Symbol,Any)*)(implicit handler: FieldConstructor, messages: play.api.i18n.MessagesProvider) + +@inputType = @{ "date" } + +@input(field, args.filter(_._1 != 'type):_*) { (id, name, value, htmlArgs) => + +} diff --git a/app/views/list.scala.html b/app/views/list.scala.html index 7710b1388..906ad1222 100644 --- a/app/views/list.scala.html +++ b/app/views/list.scala.html @@ -2,8 +2,6 @@ @import helper._ -@import b3.vertical.fieldConstructor - @**************************************** * Helper generating navigation links * ****************************************@ @@ -35,7 +33,7 @@

@Messages("computers.list.title", currentPage.total)

- @b3.form(action=routes.HomeController.list()) { + @form(action=routes.HomeController.list()) { } @@ -114,4 +112,3 @@

@Messages("computers.list.title", currentPage.total)

} - \ No newline at end of file diff --git a/build.sbt b/build.sbt index e6eb4b1b7..914d22fce 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,8 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala) libraryDependencies += guice libraryDependencies += jdbc libraryDependencies += evolutions -libraryDependencies += "com.adrianhurt" %% "play-bootstrap" % "1.0-P25-B3" -libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.0" -libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" + +libraryDependencies += "com.h2database" % "h2" % "1.4.191" +libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" +libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0-M2" % "test" diff --git a/project/build.properties b/project/build.properties index 43b8278c6..27e88aa11 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=0.13.13 diff --git a/project/plugins.sbt b/project/plugins.sbt index f99d2b516..a00ba1edb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-SNAPSHOT") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M1") From e46926a8b8e6274aa6fe8cc93153f51776ff632d Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sat, 18 Feb 2017 18:56:34 -0800 Subject: [PATCH 18/68] More upgrades (#25) * Upgrade to 2.6.x * Fix the tests --- .travis.yml | 1 + activator.properties | 4 ---- build.sbt | 7 +++---- conf/application.conf | 15 +-------------- conf/logback.xml | 34 ---------------------------------- test/FunctionalSpec.scala | 4 ++-- test/IntegrationSpec.scala | 32 ++++++++++++++++---------------- tutorial/index.html | 18 ------------------ 8 files changed, 23 insertions(+), 92 deletions(-) delete mode 100644 activator.properties delete mode 100644 conf/logback.xml delete mode 100644 tutorial/index.html diff --git a/.travis.yml b/.travis.yml index 1347ae7ce..cbb7682ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: scala scala: - 2.11.8 +- 2.12.1 jdk: - oraclejdk8 cache: diff --git a/activator.properties b/activator.properties deleted file mode 100644 index 71b0ddd98..000000000 --- a/activator.properties +++ /dev/null @@ -1,4 +0,0 @@ -name=play-anorm -title=Play Anorm -description=This is a Play CRUD application in Scala using Anorm and Play-Bootstrap. -tags=scala,play,anorm,pagination,bootstrap \ No newline at end of file diff --git a/build.sbt b/build.sbt index 914d22fce..b7b5421e4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,7 @@ name := "play-anorm" version := "0.0.1-SNAPSHOT" -scalaVersion := "2.11.8" - -resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" +scalaVersion := "2.12.1" lazy val root = (project in file(".")).enablePlugins(PlayScala) @@ -14,5 +12,6 @@ libraryDependencies += evolutions libraryDependencies += "com.h2database" % "h2" % "1.4.191" libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" -libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0-M2" % "test" +libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0-M2" % Test +libraryDependencies += "org.fluentlenium" % "fluentlenium" % "3.1.1" % Test diff --git a/conf/application.conf b/conf/application.conf index 38bcd0c0b..47045498c 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -1,16 +1,3 @@ -# This is the main configuration file for the application. -# ~~~~~ - -# Secret key -# ~~~~~ -# The secret key is used to secure cryptographics functions. -# If you deploy your application to several instances be sure to use the same key! -play.crypto.secret="changeme" - -# The application languages -# ~~~~~ -play.i18n.langs = ["en"] - # Database configuration # ~~~~~ # You can declare as many datasources as you want. @@ -20,4 +7,4 @@ db.default.url="jdbc:h2:mem:play" # Assets configuration # ~~~~~ -"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" \ No newline at end of file +"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" diff --git a/conf/logback.xml b/conf/logback.xml deleted file mode 100644 index 3df42b7a7..000000000 --- a/conf/logback.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - logs/application.log - - %date [%level] from %logger in %thread - %message%n%xException - - - - - - %coloredLevel %logger{15} - %message%n%xException{10} - - - - - - - - - - - - - - - - - - - - diff --git a/test/FunctionalSpec.scala b/test/FunctionalSpec.scala index c1c24b536..6a93f7006 100644 --- a/test/FunctionalSpec.scala +++ b/test/FunctionalSpec.scala @@ -48,7 +48,7 @@ class FunctionalSpec extends PlaySpec with OneAppPerSuite with ScalaFutures { ) status(badDateFormat) must equal(BAD_REQUEST) - contentAsString(badDateFormat) must include("""""") + contentAsString(badDateFormat) must include("""""") contentAsString(badDateFormat) must include(""" browser.goTo("http://localhost:3333/") - browser.$("header h1").first.getText must equal("Play sample application — Computer database") - browser.$("section h1").first.getText must equal("574 computers found") + browser.$("header h1").first.text() must equal("Play sample application — Computer database") + browser.$("section h1").first.text() must equal("574 computers found") - browser.$("#pagination li.current").first.getText must equal("Displaying 1 to 10 of 574") + browser.$("#pagination li.current").first.text() must equal("Displaying 1 to 10 of 574") browser.$("#pagination li.next a").click() - browser.$("#pagination li.current").first.getText must equal("Displaying 11 to 20 of 574") - browser.$("#searchbox").text("Apple") + browser.$("#pagination li.current").first.text() must equal("Displaying 11 to 20 of 574") + browser.$("#searchbox").fill().`with`("Apple") browser.$("#searchsubmit").click() - browser.$("section h1").first.getText must equal("13 computers found") + browser.$("section h1").first.text() must equal("13 computers found") browser.$("a", withText("Apple II")).click() - browser.$("section h1").first.getText must equal("Edit computer") + browser.$("section h1").first.text() must equal("Edit computer") - browser.$("#discontinued").text("") + browser.$("#discontinued").fill().`with`("") browser.$("input.primary").click() - browser.$("section h1").first.getText must equal("574 computers found") - browser.$(".alert-message").first.getText must equal("Done! Computer Apple II has been updated") + browser.$("section h1").first.text() must equal("574 computers found") + browser.$(".alert-message").first.text() must equal("Done! Computer Apple II has been updated") - browser.$("#searchbox").text("Apple") + browser.$("#searchbox").fill().`with`("Apple") browser.$("#searchsubmit").click() browser.$("a", withText("Apple II")).click() browser.$("input.danger").click() - browser.$("section h1").first.getText must equal("573 computers found") - browser.$(".alert-message").first.getText must equal("Done! Computer has been deleted") + browser.$("section h1").first.text() must equal("573 computers found") + browser.$(".alert-message").first.text() must equal("Done! Computer has been deleted") - browser.$("#searchbox").text("Apple") + browser.$("#searchbox").fill().`with`("Apple") browser.$("#searchsubmit").click() - browser.$("section h1").first.getText must equal("12 computers found") + browser.$("section h1").first.text() must equal("12 computers found") } } } -} \ No newline at end of file +} diff --git a/tutorial/index.html b/tutorial/index.html deleted file mode 100644 index e017a34a1..000000000 --- a/tutorial/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - play-anorm - - -
-

play-anorm

-

- This is a classic CRUD application, backed by a JDBC database. -

- -

- It demonstrates DB access with Anorm, classic pagination, and integration with a CSS framework (Twitter Bootstrap). -

-
- - \ No newline at end of file From cbfa327a0e0fceb48934291f7fc55c481cac19db Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 8 Mar 2017 09:25:04 -0800 Subject: [PATCH 19/68] Update fluentium-core (#29) --- build.sbt | 2 +- conf/logback.xml | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 conf/logback.xml diff --git a/build.sbt b/build.sbt index b7b5421e4..7ec847582 100644 --- a/build.sbt +++ b/build.sbt @@ -14,4 +14,4 @@ libraryDependencies += "com.h2database" % "h2" % "1.4.191" libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0-M2" % Test -libraryDependencies += "org.fluentlenium" % "fluentlenium" % "3.1.1" % Test +libraryDependencies += "org.fluentlenium" % "fluentlenium-core" % "3.1.1" % Test diff --git a/conf/logback.xml b/conf/logback.xml new file mode 100644 index 000000000..fe8bc61eb --- /dev/null +++ b/conf/logback.xml @@ -0,0 +1,34 @@ + + + + + + logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 7921212d861e308afefcff39b3552ae16e5ad46c Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 8 Mar 2017 09:53:02 -0800 Subject: [PATCH 20/68] Updated with template-control on 2017-02-19T03:39:24.490Z (#27) /LICENSE: wrote /LICENSE **/build.sbt: libraryDependencies += "com.h2database" % "h2" % "1.4.192" **/build.sbt: libraryDependencies += "com.typesafe.play" %% "anorm" % "2.6.0-M1" --- LICENSE | 11 +++++------ build.sbt | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/LICENSE b/LICENSE index 4baedcb95..b018ae2bc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,7 @@ -This software is licensed under the Apache 2 license, quoted below. +License +------- +Written in 2016 by Lightbend -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with -the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. -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. \ No newline at end of file +You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . diff --git a/build.sbt b/build.sbt index 7ec847582..843aadc14 100644 --- a/build.sbt +++ b/build.sbt @@ -10,8 +10,8 @@ libraryDependencies += guice libraryDependencies += jdbc libraryDependencies += evolutions -libraryDependencies += "com.h2database" % "h2" % "1.4.191" -libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" +libraryDependencies += "com.h2database" % "h2" % "1.4.192" +libraryDependencies += "com.typesafe.play" %% "anorm" % "2.6.0-M1" libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0-M2" % Test libraryDependencies += "org.fluentlenium" % "fluentlenium-core" % "3.1.1" % Test From 18ad12ba1d4e7e58ea781482fe8ae6803ff1f35e Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sun, 2 Apr 2017 12:16:19 -0700 Subject: [PATCH 21/68] Upgrade branch SNAPSHOT using TemplateControl (#32) * Updated with template-control on 2017-03-28T20:57:27.949Z /LICENSE: wrote /LICENSE **/build.sbt: libraryDependencies += "com.typesafe.play" %% "anorm" % "3.0.0-M1" **/build.sbt: libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M2" % Test **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M3") * Update to M3 (CSRFFilter disabled) --- app/controllers/HomeController.scala | 146 ++++++++++-------- ...yService.scala => CompanyRepository.scala} | 11 +- ...Service.scala => ComputerRepository.scala} | 28 ++-- app/models/DatabaseExecutionContext.scala | 11 ++ app/views/createForm.scala.html | 2 +- app/views/editForm.scala.html | 3 +- build.sbt | 4 +- conf/application.conf | 23 +++ conf/logback.xml | 2 +- project/plugins.sbt | 3 +- ...ntegrationSpec.scala => BrowserSpec.scala} | 2 +- test/ModelSpec.scala | 49 +++--- 12 files changed, 173 insertions(+), 111 deletions(-) rename app/models/{CompanyService.scala => CompanyRepository.scala} (72%) rename app/models/{ComputerService.scala => ComputerRepository.scala} (87%) create mode 100644 app/models/DatabaseExecutionContext.scala rename test/{IntegrationSpec.scala => BrowserSpec.scala} (98%) diff --git a/app/controllers/HomeController.scala b/app/controllers/HomeController.scala index 8bfc84adc..3a7be79c7 100644 --- a/app/controllers/HomeController.scala +++ b/app/controllers/HomeController.scala @@ -9,107 +9,123 @@ import play.api.i18n._ import play.api.mvc._ import views._ +import scala.concurrent.{ExecutionContext, Future} + /** - * Manage a database of computers - */ -class HomeController @Inject() (computerService: ComputerService, - companyService: CompanyService, - cc: ControllerComponents) + * Manage a database of computers + */ +class HomeController @Inject()(computerService: ComputerRepository, + companyService: CompanyRepository, + cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) with I18nSupport { - implicit def request2flash(implicit request: RequestHeader): Flash = request.flash - /** - * This result directly redirect to the application home. - */ + * This result directly redirect to the application home. + */ val Home = Redirect(routes.HomeController.list(0, 2, "")) - + /** - * Describe the computer form (used in both edit and create screens). - */ + * Describe the computer form (used in both edit and create screens). + */ val computerForm = Form( mapping( - "id" -> ignored(None:Option[Long]), + "id" -> ignored(None: Option[Long]), "name" -> nonEmptyText, "introduced" -> optional(date("yyyy-MM-dd")), "discontinued" -> optional(date("yyyy-MM-dd")), "company" -> optional(longNumber) )(Computer.apply)(Computer.unapply) ) - + // -- Actions /** - * Handle default path requests, redirect to computers list - */ - def index = Action { Home } - + * Handle default path requests, redirect to computers list + */ + def index = Action { + Home + } + /** - * Display the paginated list of computers. - * - * @param page Current page number (starts from 0) - * @param orderBy Column to be sorted - * @param filter Filter applied on computer names - */ - def list(page: Int, orderBy: Int, filter: String) = Action { implicit request => - Ok(html.list( - computerService.list(page = page, orderBy = orderBy, filter = ("%"+filter+"%")), - orderBy, filter - )) + * Display the paginated list of computers. + * + * @param page Current page number (starts from 0) + * @param orderBy Column to be sorted + * @param filter Filter applied on computer names + */ + def list(page: Int, orderBy: Int, filter: String) = Action.async { implicit request => + computerService.list(page = page, orderBy = orderBy, filter = ("%" + filter + "%")).map { page => + Ok(html.list(page, orderBy, filter)) + } } - + /** - * Display the 'edit form' of a existing Computer. - * - * @param id Id of the computer to edit - */ - def edit(id: Long) = Action { implicit request => - computerService.findById(id).map { computer => - Ok(html.editForm(id, computerForm.fill(computer), companyService.options)) - }.getOrElse(NotFound) + * Display the 'edit form' of a existing Computer. + * + * @param id Id of the computer to edit + */ + def edit(id: Long) = Action.async { implicit request => + computerService.findById(id).flatMap { + case Some(computer) => + companyService.options.map { options => + Ok(html.editForm(id, computerForm.fill(computer), options)) + } + case other => + Future.successful(NotFound) + } } - + /** - * Handle the 'edit form' submission - * - * @param id Id of the computer to edit - */ - def update(id: Long) = Action { implicit request => + * Handle the 'edit form' submission + * + * @param id Id of the computer to edit + */ + def update(id: Long) = Action.async { implicit request => computerForm.bindFromRequest.fold( - formWithErrors => BadRequest(html.editForm(id, formWithErrors, companyService.options)), + formWithErrors => + companyService.options.map { options => + BadRequest(html.editForm(id, formWithErrors, options)) + }, computer => { - computerService.update(id, computer) - Home.flashing("success" -> "Computer %s has been updated".format(computer.name)) + computerService.update(id, computer).map { _ => + Home.flashing("success" -> "Computer %s has been updated".format(computer.name)) + } } ) } - + /** - * Display the 'new computer form'. - */ - def create = Action { implicit request => - Ok(html.createForm(computerForm, companyService.options)) + * Display the 'new computer form'. + */ + def create = Action.async { implicit request => + companyService.options.map { options => + Ok(html.createForm(computerForm, options)) + } } - + /** - * Handle the 'new computer form' submission. - */ - def save = Action { implicit request => + * Handle the 'new computer form' submission. + */ + def save = Action.async { implicit request => computerForm.bindFromRequest.fold( - formWithErrors => BadRequest(html.createForm(formWithErrors, companyService.options)), + formWithErrors => companyService.options.map { options => + BadRequest(html.createForm(formWithErrors, options)) + }, computer => { - computerService.insert(computer) - Home.flashing("success" -> "Computer %s has been created".format(computer.name)) + computerService.insert(computer).map { _ => + Home.flashing("success" -> "Computer %s has been created".format(computer.name)) + } } ) } - + /** - * Handle computer deletion. - */ - def delete(id: Long) = Action { - computerService.delete(id) - Home.flashing("success" -> "Computer has been deleted") + * Handle computer deletion. + */ + def delete(id: Long) = Action.async { + computerService.delete(id).map { _ => + Home.flashing("success" -> "Computer has been deleted") + } } } diff --git a/app/models/CompanyService.scala b/app/models/CompanyRepository.scala similarity index 72% rename from app/models/CompanyService.scala rename to app/models/CompanyRepository.scala index 73cc19d6b..fd5bd2518 100644 --- a/app/models/CompanyService.scala +++ b/app/models/CompanyRepository.scala @@ -4,20 +4,21 @@ import javax.inject.Inject import anorm.SqlParser._ import anorm._ - import play.api.db.DBApi +import scala.concurrent.Future + case class Company(id: Option[Long] = None, name: String) @javax.inject.Singleton -class CompanyService @Inject() (dbapi: DBApi) { +class CompanyRepository @Inject()(dbapi: DBApi)(implicit ec: DatabaseExecutionContext) { private val db = dbapi.database("default") /** * Parse a Company from a ResultSet */ - val simple = { + private[models] val simple = { get[Option[Long]]("company.id") ~ get[String]("company.name") map { case id~name => Company(id, name) @@ -27,12 +28,12 @@ class CompanyService @Inject() (dbapi: DBApi) { /** * Construct the Map[String,String] needed to fill a select options set. */ - def options: Seq[(String,String)] = db.withConnection { implicit connection => + def options: Future[Seq[(String,String)]] = Future(db.withConnection { implicit connection => SQL("select * from company order by name").as(simple *). foldLeft[Seq[(String, String)]](Nil) { (cs, c) => c.id.fold(cs) { id => cs :+ (id.toString -> c.name) } } - } + })(ec) } diff --git a/app/models/ComputerService.scala b/app/models/ComputerRepository.scala similarity index 87% rename from app/models/ComputerService.scala rename to app/models/ComputerRepository.scala index 1c6684d09..3ea899797 100644 --- a/app/models/ComputerService.scala +++ b/app/models/ComputerRepository.scala @@ -7,6 +7,8 @@ import anorm.SqlParser._ import anorm._ import play.api.db.DBApi +import scala.concurrent.Future + case class Computer(id: Option[Long] = None, name: String, introduced: Option[Date], @@ -23,7 +25,7 @@ case class Page[A](items: Seq[A], page: Int, offset: Long, total: Long) { @javax.inject.Singleton -class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { +class ComputerRepository @Inject()(dbapi: DBApi, companyRepository: CompanyRepository)(implicit ec: DatabaseExecutionContext) { private val db = dbapi.database("default") @@ -32,7 +34,7 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { /** * Parse a Computer from a ResultSet */ - val simple = { + private val simple = { get[Option[Long]]("computer.id") ~ get[String]("computer.name") ~ get[Option[Date]]("computer.introduced") ~ @@ -46,7 +48,7 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { /** * Parse a (Computer,Company) from a ResultSet */ - val withCompany = simple ~ (companyService.simple ?) map { + private val withCompany = simple ~ (companyRepository.simple ?) map { case computer~company => (computer,company) } @@ -55,11 +57,11 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { /** * Retrieve a computer from the id. */ - def findById(id: Long): Option[Computer] = { + def findById(id: Long): Future[Option[Computer]] = Future { db.withConnection { implicit connection => SQL("select * from computer where id = {id}").on('id -> id).as(simple.singleOpt) } - } + }(ec) /** * Return a page of (Computer,Company). @@ -69,7 +71,7 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { * @param orderBy Computer property used for sorting * @param filter Filter applied on the name column */ - def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Page[(Computer, Option[Company])] = { + def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Future[Page[(Computer, Option[Company])]] = Future { val offest = pageSize * page @@ -104,7 +106,7 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { } - } + }(ec) /** * Update a computer. @@ -112,7 +114,7 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { * @param id The computer id * @param computer The computer values. */ - def update(id: Long, computer: Computer) = { + def update(id: Long, computer: Computer) = Future { db.withConnection { implicit connection => SQL( """ @@ -128,14 +130,14 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { 'company_id -> computer.companyId ).executeUpdate() } - } + }(ec) /** * Insert a new computer. * * @param computer The computer values. */ - def insert(computer: Computer) = { + def insert(computer: Computer) = Future { db.withConnection { implicit connection => SQL( """ @@ -151,17 +153,17 @@ class ComputerService @Inject() (dbapi: DBApi, companyService: CompanyService) { 'company_id -> computer.companyId ).executeUpdate() } - } + }(ec) /** * Delete a computer. * * @param id Id of the computer to delete. */ - def delete(id: Long) = { + def delete(id: Long) = Future { db.withConnection { implicit connection => SQL("delete from computer where id = {id}").on('id -> id).executeUpdate() } - } + }(ec) } diff --git a/app/models/DatabaseExecutionContext.scala b/app/models/DatabaseExecutionContext.scala new file mode 100644 index 000000000..b94a714a6 --- /dev/null +++ b/app/models/DatabaseExecutionContext.scala @@ -0,0 +1,11 @@ +package models + +import javax.inject.Inject + +import akka.actor.ActorSystem +import play.api.libs.concurrent.CustomExecutionContext + +/** + * + */ +class DatabaseExecutionContext @Inject()(system: ActorSystem) extends CustomExecutionContext(system, "database.dispatcher") diff --git a/app/views/createForm.scala.html b/app/views/createForm.scala.html index 7e1e934fe..57a4c0e14 100644 --- a/app/views/createForm.scala.html +++ b/app/views/createForm.scala.html @@ -1,4 +1,4 @@ -@(computerForm: Form[Computer], companies: Seq[(String, String)])(implicit messages: Messages) +@(computerForm: Form[Computer], companies: Seq[(String, String)])(implicit messages: Messages, requestHeader: RequestHeader) @import views.html.helper._ diff --git a/app/views/editForm.scala.html b/app/views/editForm.scala.html index e51a3ca16..8ba90076f 100644 --- a/app/views/editForm.scala.html +++ b/app/views/editForm.scala.html @@ -1,4 +1,4 @@ -@(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)])(implicit messages: Messages) +@(id: Long, computerForm: Form[Computer], companies : Seq[(String, String)])(implicit messages: Messages, requestHeader: RequestHeader) @import views.html.helper._ @@ -9,7 +9,6 @@

Edit computer

@form(routes.HomeController.update(id)) {
- @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") @date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") @date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") diff --git a/build.sbt b/build.sbt index 843aadc14..f6a6636cd 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -name := "play-anorm" +name := "play-scala-anorm-example" version := "0.0.1-SNAPSHOT" @@ -12,6 +12,6 @@ libraryDependencies += evolutions libraryDependencies += "com.h2database" % "h2" % "1.4.192" libraryDependencies += "com.typesafe.play" %% "anorm" % "2.6.0-M1" -libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "2.0.0-M2" % Test +libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M2" % Test libraryDependencies += "org.fluentlenium" % "fluentlenium-core" % "3.1.1" % Test diff --git a/conf/application.conf b/conf/application.conf index 47045498c..8eb69f118 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -8,3 +8,26 @@ db.default.url="jdbc:h2:mem:play" # Assets configuration # ~~~~~ "assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600" + + +# Number of database connections +# See https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing +fixedConnectionPool = 9 + +play.db { + prototype { + hikaricp.minimumIdle = ${fixedConnectionPool} + hikaricp.maximumPoolSize = ${fixedConnectionPool} + } +} + +# Job queue sized to HikariCP connection pool +database.dispatcher { + executor = "thread-pool-executor" + throughput = 1 + thread-pool-executor { + fixed-pool-size = ${fixedConnectionPool} + } +} + +play.filters.disabled+=play.filters.csrf.CSRFFilter diff --git a/conf/logback.xml b/conf/logback.xml index fe8bc61eb..3df42b7a7 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -31,4 +31,4 @@ - \ No newline at end of file + diff --git a/project/plugins.sbt b/project/plugins.sbt index a00ba1edb..0f16cca01 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,5 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M1") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M3") + diff --git a/test/IntegrationSpec.scala b/test/BrowserSpec.scala similarity index 98% rename from test/IntegrationSpec.scala rename to test/BrowserSpec.scala index 41963a6d0..32aec7121 100644 --- a/test/IntegrationSpec.scala +++ b/test/BrowserSpec.scala @@ -5,7 +5,7 @@ import org.scalatestplus.play.PlaySpec import org.scalatestplus.play._ -class IntegrationSpec extends PlaySpec { +class BrowserSpec extends PlaySpec { "Application" should { diff --git a/test/ModelSpec.scala b/test/ModelSpec.scala index 37797d913..bcefab19b 100644 --- a/test/ModelSpec.scala +++ b/test/ModelSpec.scala @@ -1,12 +1,13 @@ -import models.ComputerService -import org.scalatestplus.play._ - -class ModelSpec extends PlaySpec with OneAppPerSuite { - var computerService: ComputerService = app.injector.instanceOf(classOf[ComputerService]) +import org.scalatest.concurrent.ScalaFutures +import org.scalatestplus.play._ +import org.scalatestplus.play.guice.GuiceOneAppPerSuite +class ModelSpec extends PlaySpec with GuiceOneAppPerSuite with ScalaFutures { import models._ + import scala.concurrent.ExecutionContext.Implicits.global + // -- Date helpers def dateIs(date: java.util.Date, str: String) = { @@ -14,39 +15,47 @@ class ModelSpec extends PlaySpec with OneAppPerSuite { } // -- - + + def computerService: ComputerRepository = app.injector.instanceOf(classOf[ComputerRepository]) + "Computer model" should { - + "be retrieved by id" in { - val macintosh = computerService.findById(21).get - + whenReady(computerService.findById(21)) { maybeComputer => + val macintosh = maybeComputer.get + macintosh.name must equal("Macintosh") macintosh.introduced.value must matchPattern { case date:java.util.Date if dateIs(date, "1984-01-24") => } + } } "be listed along its companies" in { + whenReady(computerService.list()) { computers => - val computers = computerService.list() - - computers.total must equal(574) - computers.items must have length(10) + computers.total must equal(574) + computers.items must have length(10) + } } "be updated if needed" in { + val result = computerService.findById(21).flatMap { computer => computerService.update(21, Computer(name="The Macintosh", introduced=None, discontinued=None, - companyId=Some(1))) - - val macintosh = computerService.findById(21).get - + companyId=Some(1))).flatMap { _ => + computerService.findById(21) + } + } + + whenReady(result) { computer => + val macintosh = computer.get + macintosh.name must equal("The Macintosh") macintosh.introduced mustBe None + } } - } - -} \ No newline at end of file +} From a15bf894951d128186e1da670c1838d910d60ee1 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Mon, 3 Apr 2017 08:39:28 -0700 Subject: [PATCH 22/68] Upgrade branch SNAPSHOT using TemplateControl (#33) * Updated with template-control on 2017-04-02T19:30:52.974Z /LICENSE: wrote /LICENSE **/build.sbt: libraryDependencies += "com.typesafe.play" %% "anorm" % "3.0.0-M1" * Revert to 2.6.0-M1 as 3.0.0 has no 2.12 version * fix travis --- .travis.yml | 12 +++++++++--- test/FunctionalSpec.scala | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index cbb7682ba..38d820bd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ language: scala +dist: trusty +sudo: true +group: beta scala: - 2.11.8 - 2.12.1 @@ -7,10 +10,13 @@ jdk: cache: directories: - "$HOME/.ivy2/cache" + - "$HOME/.sbt/boot/" before_cache: -- rm -rf $HOME/.ivy2/cache/com.typesafe.play/* -- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/* -- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + # Delete all ivydata files since ivy touches them on each build + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm + # Delete any SBT lock files + - find $HOME/.sbt -name "*.lock" -delete notifications: slack: secure: a738ADtNq3eN26B6LCcd+NY83s+PQjQagL0S9HhBFvCpvzkQ/CwAK9l5keWayM2S4IUBOmsS/uMuKodmP7QoPN3XxUGgsZNTMjHT1FAa/rc6N2MKF5C20fBII1/cJOuXBZ5x+2EW+odch3+0pafMCXI/RZZLiHELD2Jx6JGS0lQ= diff --git a/test/FunctionalSpec.scala b/test/FunctionalSpec.scala index 6a93f7006..bcb6ea0bf 100644 --- a/test/FunctionalSpec.scala +++ b/test/FunctionalSpec.scala @@ -4,8 +4,9 @@ import org.scalatest.concurrent.ScalaFutures import play.api.test._ import play.api.test.Helpers._ import org.scalatestplus.play._ +import org.scalatestplus.play.guice._ -class FunctionalSpec extends PlaySpec with OneAppPerSuite with ScalaFutures { +class FunctionalSpec extends PlaySpec with GuiceOneAppPerSuite with ScalaFutures { def dateIs(date: java.util.Date, str: String) = { new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str From d40b7adaada7ada7c13c6a8d370283f3a2ecfa34 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 4 Apr 2017 07:47:28 -0700 Subject: [PATCH 23/68] Set anorm to 2.5.0 (#36) * set anorm to 2.5.0 * Update to anorm 2.5.3 --- .travis.yml | 1 - build.sbt | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 38d820bd9..4672a83e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ sudo: true group: beta scala: - 2.11.8 -- 2.12.1 jdk: - oraclejdk8 cache: diff --git a/build.sbt b/build.sbt index f6a6636cd..514fa8ddf 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ name := "play-scala-anorm-example" version := "0.0.1-SNAPSHOT" -scalaVersion := "2.12.1" +scalaVersion := "2.11.8" lazy val root = (project in file(".")).enablePlugins(PlayScala) @@ -11,7 +11,9 @@ libraryDependencies += jdbc libraryDependencies += evolutions libraryDependencies += "com.h2database" % "h2" % "1.4.192" -libraryDependencies += "com.typesafe.play" %% "anorm" % "2.6.0-M1" + +// 2.6.0-M1 is not final yet, and 2.5.x does not run on scala 2.12 +libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M2" % Test libraryDependencies += "org.fluentlenium" % "fluentlenium-core" % "3.1.1" % Test From 7e49ba167aba5b7f1b9f497e667eb4d3042a5284 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 4 Apr 2017 08:03:17 -0700 Subject: [PATCH 24/68] Remove date per PR 31 (#37) --- app/views/createForm.scala.html | 4 ++-- app/views/editForm.scala.html | 4 ++-- test/BrowserSpec.scala | 6 ++++++ test/FunctionalSpec.scala | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/views/createForm.scala.html b/app/views/createForm.scala.html index 57a4c0e14..91810d747 100644 --- a/app/views/createForm.scala.html +++ b/app/views/createForm.scala.html @@ -10,8 +10,8 @@

Add a computer

@inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") - @date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") - @date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + @inputText(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @inputText(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") @select( computerForm("company"), diff --git a/app/views/editForm.scala.html b/app/views/editForm.scala.html index 8ba90076f..328f2a85a 100644 --- a/app/views/editForm.scala.html +++ b/app/views/editForm.scala.html @@ -10,8 +10,8 @@

Edit computer

@inputText(computerForm("name"), '_label -> "Computer name", '_help -> "") - @date(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") - @date(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") + @inputText(computerForm("introduced"), '_label -> "Introduced date", '_help -> "") + @inputText(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "") @select( computerForm("company"), diff --git a/test/BrowserSpec.scala b/test/BrowserSpec.scala index 32aec7121..7c7c2852d 100644 --- a/test/BrowserSpec.scala +++ b/test/BrowserSpec.scala @@ -29,6 +29,12 @@ class BrowserSpec extends PlaySpec { browser.$("section h1").first.text() must equal("Edit computer") + browser.$("#discontinued").fill().`with`("xxx") + browser.$("input.primary").click() + + browser.$("dl.error").size must equal(1) + browser.$("dl.error label").first.text() must equal("Discontinued date") + browser.$("#discontinued").fill().`with`("") browser.$("input.primary").click() diff --git a/test/FunctionalSpec.scala b/test/FunctionalSpec.scala index bcb6ea0bf..20ac57bb1 100644 --- a/test/FunctionalSpec.scala +++ b/test/FunctionalSpec.scala @@ -50,7 +50,7 @@ class FunctionalSpec extends PlaySpec with GuiceOneAppPerSuite with ScalaFutures status(badDateFormat) must equal(BAD_REQUEST) contentAsString(badDateFormat) must include("""""") - contentAsString(badDateFormat) must include(""" Date: Tue, 18 Apr 2017 13:53:00 -0700 Subject: [PATCH 25/68] Updated with template-control on 2017-04-18T20:11:50.468Z (#44) /LICENSE: wrote /LICENSE **/build.sbt: scalaVersion := "2.12.2" **/build.sbt: libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M3" % Test **/build.properties: sbt.version=0.13.15 **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") --- build.sbt | 4 ++-- project/build.properties | 2 +- project/plugins.sbt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 514fa8ddf..eb144b63d 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ name := "play-scala-anorm-example" version := "0.0.1-SNAPSHOT" -scalaVersion := "2.11.8" +scalaVersion := "2.12.2" lazy val root = (project in file(".")).enablePlugins(PlayScala) @@ -14,6 +14,6 @@ libraryDependencies += "com.h2database" % "h2" % "1.4.192" // 2.6.0-M1 is not final yet, and 2.5.x does not run on scala 2.12 libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" -libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M2" % Test +libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M3" % Test libraryDependencies += "org.fluentlenium" % "fluentlenium-core" % "3.1.1" % Test diff --git a/project/build.properties b/project/build.properties index 27e88aa11..64317fdae 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.13 +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index 0f16cca01..5a4c19dd0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M3") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") From 7e5b54af43ab93a0136dbf1fb4a02ab86ff8b801 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 18 Apr 2017 15:13:34 -0700 Subject: [PATCH 26/68] Updated with template-control on 2017-04-18T22:00:25.638Z (#45) /LICENSE: wrote /LICENSE **/build.sbt: libraryDependencies += "com.h2database" % "h2" % "1.4.194" --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index eb144b63d..e5fdb01ae 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ libraryDependencies += guice libraryDependencies += jdbc libraryDependencies += evolutions -libraryDependencies += "com.h2database" % "h2" % "1.4.192" +libraryDependencies += "com.h2database" % "h2" % "1.4.194" // 2.6.0-M1 is not final yet, and 2.5.x does not run on scala 2.12 libraryDependencies += "com.typesafe.play" %% "anorm" % "2.5.3" From 493821a8d97cc7ef21a7428c29c38a0deb7d5098 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Tue, 25 Apr 2017 17:20:14 -0700 Subject: [PATCH 27/68] Use singleton dbexecutioncontext (#46) --- app/models/DatabaseExecutionContext.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/DatabaseExecutionContext.scala b/app/models/DatabaseExecutionContext.scala index b94a714a6..ef2c77035 100644 --- a/app/models/DatabaseExecutionContext.scala +++ b/app/models/DatabaseExecutionContext.scala @@ -1,11 +1,13 @@ package models -import javax.inject.Inject +import javax.inject._ import akka.actor.ActorSystem import play.api.libs.concurrent.CustomExecutionContext /** - * + * This class is a pointer to an execution context configured to point to "database.dispatcher" + * in the "application.conf" file. */ +@Singleton class DatabaseExecutionContext @Inject()(system: ActorSystem) extends CustomExecutionContext(system, "database.dispatcher") From 08c9b11ee445f77ece44e326578fb461ac082f84 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Thu, 27 Apr 2017 19:08:55 -0700 Subject: [PATCH 28/68] Updated with template-control on 2017-04-28T01:46:59.262Z (#48) /LICENSE: wrote /LICENSE **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 5a4c19dd0..d87690a00 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M4") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") From 8b960a5e5a379feedc972913f1292999c95c8e18 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Sun, 4 Jun 2017 00:21:05 +0200 Subject: [PATCH 29/68] Updated with template-control on 2017-06-03T16:01:09.418Z (#52) **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d87690a00..fa74539dd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-M5") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0-RC2") From d6a47de91ef26f1eea335f2914c50a39ec42bf55 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Fri, 23 Jun 2017 12:52:19 -0700 Subject: [PATCH 30/68] Upgrade branch 2.6.x using TemplateControl (#54) * Updated with template-control on 2017-06-23T02:18:35.871Z **/plugins.sbt: addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") * Fix braces in template --- app/views/list.scala.html | 30 +++++++++++++++--------------- project/plugins.sbt | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/views/list.scala.html b/app/views/list.scala.html index 906ad1222..850d62eaa 100644 --- a/app/views/list.scala.html +++ b/app/views/list.scala.html @@ -9,20 +9,20 @@ routes.HomeController.list(newPage, newOrderBy.map { orderBy => if(orderBy == scala.math.abs(currentOrderBy)) -currentOrderBy else orderBy }.getOrElse(currentOrderBy), currentFilter) - + } @********************************** * Helper generating table headers * ***********************************@ @header(orderBy: Int, title: String) = { - + @title } @main { - +

@Messages("computers.list.title", currentPage.total)

@flash.get("success").map { message => @@ -32,18 +32,18 @@

@Messages("computers.list.title", currentPage.total)

}
- + @form(action=routes.HomeController.list()) { } - + Add a new computer - +
- + @Option(currentPage.items).filterNot(_.isEmpty).map { computers => - + @@ -55,7 +55,7 @@

@Messages("computers.list.title", currentPage.total)

- @computers.map { + @computers.map { case (computer, company) => { @@ -80,7 +80,7 @@

@Messages("computers.list.title", currentPage.total)

@currentPage.prev.map { page => + }.getOrElse { + }.getOrElse {
@computer.name