Skip to content
This repository has been archived by the owner on May 9, 2019. It is now read-only.

Add Search UI #113

Merged
merged 1 commit into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ lazy val userImpl = (project in file("user-impl"))
lazy val webGateway = (project in file("web-gateway"))
.settings(commonSettings: _*)
.enablePlugins(PlayScala, LagomPlay, SbtReactiveAppPlugin)
.dependsOn(biddingApi, itemApi, userApi)
.dependsOn(biddingApi, itemApi, searchApi, userApi)
.settings(
libraryDependencies ++= Seq(
lagomScaladslServer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.example.elasticsearch.IndexedItem
import com.example.elasticsearch.request._
import com.example.elasticsearch.response._
import com.lightbend.lagom.scaladsl.api.ServiceCall
import play.api.libs.json.Json

import scala.concurrent.ExecutionContext.Implicits.global

Expand Down
5 changes: 4 additions & 1 deletion web-gateway/app/Loader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package loader

import com.example.auction.bidding.api.BiddingService
import com.example.auction.item.api.ItemService
import com.example.auction.search.api.SearchService
import com.example.auction.user.api.UserService
import com.lightbend.lagom.scaladsl.api.{LagomConfigComponent, ServiceAcl, ServiceInfo}
import com.lightbend.lagom.scaladsl.client.LagomServiceClientComponents
import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents
import com.lightbend.rp.servicediscovery.lagom.scaladsl.LagomServiceLocatorComponents
import com.softwaremill.macwire._
import controllers.{AssetsComponents, ItemController, Main, ProfileController}
import controllers.{AssetsComponents, ItemController, Main, ProfileController, SearchController}
import play.api.ApplicationLoader.Context
import play.api.libs.ws.ahc.AhcWSComponents
import play.api.{ApplicationLoader, BuiltInComponentsFromContext, Mode}
Expand Down Expand Up @@ -41,10 +42,12 @@ abstract class WebGateway(context: Context) extends BuiltInComponentsFromContext
lazy val userService = serviceClient.implement[UserService]
lazy val itemService = serviceClient.implement[ItemService]
lazy val biddingService = serviceClient.implement[BiddingService]
lazy val searchService = serviceClient.implement[SearchService]

lazy val main = wire[Main]
lazy val itemController = wire[ItemController]
lazy val profileController = wire[ProfileController]
lazy val searchController = wire[SearchController]
}

class WebGatewayLoader extends ApplicationLoader {
Expand Down
78 changes: 78 additions & 0 deletions web-gateway/app/controllers/SearchController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package controllers

import com.example.auction.search.api.{SearchRequest, SearchService}
import com.example.auction.user.api.UserService
import com.example.auction.utils.PaginatedSequence
import com.typesafe.config.Config
import play.api.data.Forms.{nonEmptyText, _}
import play.api.data.{Form, Mapping}
import play.api.mvc.{ControllerComponents, _}

import scala.concurrent.{ExecutionContext, Future}

class SearchController(config: Config,
searchService: SearchService,
userService: UserService,
controllerComponents: ControllerComponents)(implicit ec: ExecutionContext)
extends AbstractAuctionController(userService, controllerComponents) {

private val showInlineInstruction: Boolean = config.getBoolean("online-auction.instruction.show")
private val pageSize: Int = config.getInt("items-search.page-size")

def searchForm(): Action[AnyContent] = Action.async { implicit request =>
requireUser(loadNav(_).map { implicit nav =>
Ok(views.html.searchItems(showInlineInstruction = showInlineInstruction,
form = SearchItemsForm.form.fill(SearchItemsForm()),
optionalSearchItemPaginatedSequence = None))
})
}

def search(): Action[AnyContent] = Action.async { implicit request =>
requireUser(user =>
loadNav(user).flatMap { implicit nav =>
SearchItemsForm.form.bindFromRequest().fold(
errorForm => {
Future.successful(Ok(views.html.searchItems(
showInlineInstruction = showInlineInstruction,
form = errorForm,
optionalSearchItemPaginatedSequence = None)))
},
searchItemsForm => {
searchService.search(searchItemsForm.pageNumber, pageSize)
.invoke(SearchRequest(
if (searchItemsForm.keywords.isEmpty) None else Some(searchItemsForm.keywords),
Some(searchItemsForm.maximumPrice.intValue()),
Some(searchItemsForm.currency.name)))
.map(searchResponse => {
Ok(views.html.searchItems(
showInlineInstruction = showInlineInstruction,
form = SearchItemsForm.form.fill(searchItemsForm),
optionalSearchItemPaginatedSequence = Some(PaginatedSequence(
searchResponse.items,
searchResponse.pageNo,
searchResponse.pageSize,
searchResponse.numResults))))
})
})
})
}
}


case class SearchItemsForm(keywords: String = "",
maximumPrice: BigDecimal = 0.0,
currency: Currency = Currency.USD,
pageNumber: Int = 0)

object SearchItemsForm {
val currency: Mapping[Currency] = nonEmptyText
.verifying("invalid.currency", c => Currency.isDefined(c))
.transform[Currency](Currency.valueOf, _.name)
val form = Form(mapping(
"keywords" -> text,
"maximumPrice" -> bigDecimal,
"currency" -> currency,
"pageNumber" -> number(min = 0)
)(SearchItemsForm.apply)(SearchItemsForm.unapply))
}

1 change: 1 addition & 0 deletions web-gateway/app/views/main.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
</div>
<div class="top-bar-right">
<ul class="menu dropdown" data-dropdown-menu>
<li><a href="@routes.SearchController.searchForm()">@Messages("search")</a></li>
<li>
<a href="@routes.ProfileController.myItems(ItemStatus.Created.toString.toLowerCase(Locale.ENGLISH), None)">
@nav.user.map(_.name).getOrElse(Messages("logInAs"))</a>
Expand Down
122 changes: 122 additions & 0 deletions web-gateway/app/views/searchItems.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
@import com.example.auction.item.api.ItemStatus
@import com.example.auction.item.api.ItemSummary
@import com.example.auction.search.api.SearchItem
@import com.example.auction.utils
@import controllers.Currency
@import helper._
@import controllers.{Currency => Currencies}

@import java.util.Locale

@import com.example.auction.utils.PaginatedSequence
@(showInlineInstruction: Boolean,
form: Form[SearchItemsForm],
optionalSearchItemPaginatedSequence: Option[PaginatedSequence[SearchItem]]
)(implicit nav: Nav, request: RequestHeader)

@main(Messages("searchItems")) {

<h2>@Messages("searchItems")</h2>

@if(showInlineInstruction) {
<p>@Messages("instruction.search")</p>

<p>@Html(Messages("instruction.enableSearch", "<a href=\"https://github.com/lagom/online-auction-scala/blob/master/README.md\">README.md</a>"))</p>
}

@foundationForm(form, routes.SearchController.search()) {
<div class="row">
<div class="medium-6 columns">
@inputText(form("keywords"))
</div>
</div>
<div class="column row">
@inputText(form("maximumPrice"), 'type -> "number", 'min -> 0, 'value -> form("maximumPrice").value)
@select(
field = form("currency"),
options = Currencies.values.map(c => c.name -> c.getDisplayName).sortBy(_._2)
)
</div>
<input type="hidden" id="pageNumber" value="0" name="@form("pageNumber").name"/>

<input class="button" type="submit" value="@Messages("searchItems")"/>
}
@if(optionalSearchItemPaginatedSequence.isDefined) {
@defining(optionalSearchItemPaginatedSequence.get) { searchItemPaginatedSequence: PaginatedSequence[SearchItem] =>

@if(!searchItemPaginatedSequence.isEmpty) {
<script>
var updatePage = function (pageNum) {
document.getElementById("pageNumber").value = pageNum;
document.getElementById("searchForm").submit();
}
</script>

@if(searchItemPaginatedSequence.count < searchItemPaginatedSequence.pageSize) {
<div>@Messages("searchFoundCount", searchItemPaginatedSequence.count)</div>
} else {
<div>@Messages("searchFoundCountWithPagination",
searchItemPaginatedSequence.page * searchItemPaginatedSequence.pageSize + 1,
searchItemPaginatedSequence.page * searchItemPaginatedSequence.pageSize + searchItemPaginatedSequence.items.size,
searchItemPaginatedSequence.count)</div>
}

<table>

<thead>
<tr>
<th>@Messages("title")</th>
<th>@Messages("status")</th>
<th>@Messages("currentPrice")</th>
</tr>
</thead>

<tbody>
@for(item <- searchItemPaginatedSequence.items) {
<tr>
<td>
<a href="@routes.ItemController.getItem(item.id)">@item.title</a>
</td>
<td>@item.itemStatus</td>
<td>
@if(
item.itemStatus.equals(ItemStatus.Auction.toString)
&& item.price.isDefined
&& item.price.get > 0) {
@Currency.valueOf(item.currencyId).format(item.price.get)
} else {
-
}
</td>
</tr>
<tr>
<td colspan="4">@item.description</td>
</tr>
}
</tbody>
</table>
@if(searchItemPaginatedSequence.count > searchItemPaginatedSequence.pageSize * (searchItemPaginatedSequence.page + 1)) {
@foundationForm(form, routes.SearchController.search()) {
@defining(form("keywords")) { field: Field =>
<input type="hidden" id="@field.id" value="@field.value" name="@field.name"/>
}
@defining(form("maximumPrice")) { field: Field =>
<input type="hidden" id="@field.id" value="@field.value" name="@field.name"/>
}
@defining(form("currency")) { field: Field =>
<input type="hidden" id="@field.id" value="@field.value" name="@field.name"/>
}
@defining(form("pageNumber")) { field: Field =>
<input type="hidden" id="@field.id" value="@(searchItemPaginatedSequence.page + 1)" name="@field.name"/>
}

<input class="button" type="submit" value="@Messages("nextResultPage")"/>
}
}

}
}
}
}


4 changes: 4 additions & 0 deletions web-gateway/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

lagom.circuit-breaker.default.call-timeout = 5s

online-auction.instruction.show = false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the java app defaults this to true, however, I've seen no traces of instructions in the scala implementation. Do we want to show the instructions?


play.application.loader = loader.WebGatewayLoader
play.filters.headers.contentSecurityPolicy = "img-src 'self' data:; default-src 'self'"

items-search.page-size = 3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty low, but I wanted the pagination features to be easily demonstrable. Let me know if you think another value is more appropriate.

12 changes: 12 additions & 0 deletions web-gateway/conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ createItem=Create item

invalid.increment=The increment must be a multiple of 0.5.
invalid.reserve=The reserve must be a multiple of 0.5.
invalid.currency=Please enter a valid currency

seller=Seller
auctionStart=Auction start
Expand Down Expand Up @@ -69,3 +70,14 @@ items.caption={0} ({1})

pagination.loadMore=Load more
pagination.reload=Reload

search=Search
searchItems=Search Items
instruction.search=Here you can search for auctioned items. You can filter your search by specifying the maximum price of items to search for and the currency to search in.
instruction.enableSearch=Search functionality can be enabled by downloading and running a local instance of elasticsearch. Steps for doing this can be found in {0}. If the lagom system is up and running, you don''t need to restart the system after installing and running elasticsearch; the search service becomes functional once it starts.
keywords=Exact terms:
maximumPrice=Maximum Price (set to '0' to disable filter)
currency=
searchFoundCount={0} items found
searchFoundCountWithPagination=Displaying {0} - {1} of {2} items found
nextResultPage=More
2 changes: 2 additions & 0 deletions web-gateway/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ GET /item/:id controllers.ItemController.getItem(id: j
POST /item/:id/auction controllers.ItemController.startAuction(id: java.util.UUID)
POST /item/:id/bid controllers.ItemController.placeBid(id: java.util.UUID)

GET /search controllers.SearchController.searchForm
POST /search controllers.SearchController.search
GET /assets/*file controllers.Assets.at(path = "/public", file)