Im trying to make an order form with Play 2 and Scala.
Here is what it was before grouping:
<table>
#items.zipWithIndex.map {
case (item, index) =>
#itemRow(item, index)
}
</table>
itemRow definition
#itemRow(index: Int, item: Item) = {
<tr>
<td>
#(index+1)
</td>
<td>
#item.name
</td>
<td>
<input type="hidden" name="#requestForm("items")("[" + index + "]")("itemId").name" value="#item.id">
<input type="text" name="items[#index].count" value="#requestForm("items")("[" + index + "]")("count").value">
</td>
</tr>
}
At first I tried naive implementation
#items.groupBy(item => item.category).map {
case (categoryId, itemsInCategory) =>
<table>
#itemsInCategory.zipWithIndex.map {
case (item, index) =>
#itemRow(item, index)
}
</table>
}
But there is a problem, indexes in each category starts with 0.
So, http request is something like that:
# category 1
items[0].id = 1
items[0].count = 1
items[1].id = 2
items[1].count = 2
# category 2
items[0].id = 3
items[0].count = 1
items[1].id = 4
items[1].count = 5
And it is causes to values being overriden.
I need my indexes for be consecutive within the form, like this:
# category 1
items[0].id = 1
items[0].count = 1
items[1].id = 2
items[1].count = 2
# category 2
items[2].id = 3
items[2].count = 1
items[3].id = 4
items[3].count = 5
So there is questions
For functional programmers:
Can I make index variable shared for all groups?
For Play 2.0 or web programmers:
Is there another way to make form with variable count of repeated values?
How to avoid sending this bunch of items with 0 count?
I have no experience with Play so I can't comment on the Play specific questions (maybe it already provides helpers for what you want), but on the scala librayr alone you can do something like this:
#items.sortBy(item => item.category).zipWithIndex.groupBy{ case (item, _) => item.category}.map {
case (categoryId, indexedItemsInCategory) =>
<table>
#indexedItemsInCategory.map {
case (item, index) =>
#itemRow(item, index)
}
</table>
}
The idea is to first sort the items by category and then zip them with the corresponding indexes. Then only you group them by category (which should be fast as the list is already sorted).
Related
Question is - how should I take data from this kind of inputs and pass it properly to DB (by SORM framework)?
I try to pass data to two tables in my DB, the result of my work is that while the values are properly inserted to the first table (Author), second one (Book) remains untouched.
Probably my problem is that I am not propely naming nested, repeated inputs on my page, but i cant find proper way to do this (I'm fairly new to Scala, so I probably lack of experience on this).
Ok(Json.toJson(author))
in addData in Application.scala shows me
{"name":"what","books":[]}
so I think, that the problem is in binding data from request.
I tried to follow examples here: How to model an entity with many children in Sorm? and here: https://www.playframework.com/documentation/2.1.1/ScalaForms, by operating on the template "play-scala" from Play framework, so i've got code like this:
Models are:
case class Author(name: String, books: Seq[Book]) {
}
object Author {
implicit val authorFormat = Json.format[Author]
}
case class Book(title: String ) {
}
object Book {
implicit val bookFormat = Json.format[Book]
}
case class AuthorBook(name: Author, title: Book) {
}
The scala.index.html
<table>
<tr>
<td>
On the left Author, on the right Book
<td>
</tr>
<tr>
<td>
<ul id="authors"></ul>
</td>
<td>
<ul id="books"></ul>
</td>
</tr>
</table>
<table>
<form action="#routes.Application.addData()" method ="post">
<tr>
<td>Author: <input name="author" type="text">
</td>
<td>Books: <input name="books.title[0]" type="text"><br><input name="books.title[1]" type="text">
</td>
</tr>
<tr>
<td>
<button>Submit</button>
</td>
</tr>
</form>
</table>
And Application.scala
class Application extends Controller {
def index = Action {
Ok(views.html.index("E-Library"))
}
val authorForm: Form[Author] = Form {
mapping(
"author" -> text,
"books" -> seq(
mapping(
"title" -> text)(Book.apply)(Book.unapply)
)
)(Author.apply)(Author.unapply)
}
def error = Action {
Ok("error")
}
def addData = Action { implicit request =>
authorForm.bindFromRequest.fold(
formWithErrors => {
BadRequest("Bad request!")
},
authorF => {
val author = DB.save(authorF)
Ok(Json.toJson(author))
//Redirect(routes.Application.index())
}
)
}
def getAuthor = Action {
val dataAuthor = DB.query[Author].fetch
Ok(Json.toJson(dataAuthor))
}
def getBook = Action {
val dataBook = DB.query[Book].fetch
Ok(Json.toJson(dataBook))
}
def getData = Action {
Redirect(routes.Application.index())
}
}
Found it!
As mentioned here: Nested form in Play! Scala 2.2
I needed to rename input names in my form from
book.title[0]
to
book[0].title.
I am writing my first application in Java. In one of my views, I have a couple of helper functions:
#**********************************
* Helper generating table columns *
***********************************#
#tableColumn(content:String) = {
<td>
#content
</td>
}
and
#**********************************
* Helper to convert boolean to string *
***********************************#
#convertBooleanToString(flag:Boolean) {
if (flag) {
"Yes"
} else {
"No"
}
}
I am trying to use these 2 functions as below but getting compiler error.
<tr>
<td>Completed</td>
#for(item <- items) {
#tableColumn(convertBooleanToString(item.isComplete))
}
</tr>
the error that i get is as below:
illegal start of simple expression
Can you please help?
While reproducing I don't get your error message. Please give more code.
But for now you can try:
#**********************************
* Helper generating table columns *
***********************************#
#tableColumn(content:String) = {
<td>
#content
</td>
}
#**********************************
* Helper to convert boolean to string *
***********************************#
#convertBooleanToString(flag:Boolean) = #{if (flag) "Yes" else "No"}
<tr>
<td>Completed</td>
#for(item <- items) {
#tableColumn(convertBooleanToString(item.isComplete))
}
</tr>
In Play! 1, it was possible to get the current index inside a loop, with the following code:
#{list items:myItems, as: 'item'}
<li>Item ${item_index} is ${item}</li>
#{/list}
Is there an equivalent in Play2, to do something like that?
#for(item <- myItems) {
<li>Item ??? is #item</li>
}
Same question for the _isLast and _isFirst.
ps: this question is quite similar, but the solution implied to modify the code to return a Tuple (item, index) instead of just a list of item.
Yes, zipWithIndex is built-in feature fortunately there's more elegant way for using it:
#for((item, index) <- myItems.zipWithIndex) {
<li>Item #index is #item</li>
}
The index is 0-based, so if you want to start from 1 instead of 0 just add 1 to currently displayed index:
<li>Item #{index+1} is #item</li>
PS: Answering to your other question - no, there's no implicit indexes, _isFirst, _isLast properties, anyway you can write simple Scala conditions inside the loop, basing on the values of the zipped index (Int) and size of the list (Int as well).
#for((item, index) <- myItems.zipWithIndex) {
<div style="margin-bottom:20px;">
Item #{index+1} is #item <br>
#if(index == 0) { First element }
#if(index == myItems.size-1) { Last element }
#if(index % 2 == 0) { ODD } else { EVEN }
</div>
}
The answer in the linked question is basically what you want to do. zipWithIndex converts your list (which is a Seq[T]) into a Seq[(T, Int)]:
#list.zipWithIndex.foreach{case (item, index) =>
<li>Item #index is #item</li>
}
*UPDATE* (see below)
I understand the basics of KnockoutJS. When creating a table viewmodel, one would use <tr data-bind="foreach: rows">
Now I'm trying to abstract the table viewmodel, so that I can create multiple tables with the same behaviour (sorting, editing, pagination, etc..). So what I'm aiming for is something like this:
HTML
<div class="table a" data-bind="myTableBinding: aTableViewModel"></div>
<div class="table b" data-bind="myTableBinding: anotherTableViewmodel"></div>
Main ViewModel
var MainViewModel = function () {
this.aTableViewModel = new AbstractTableViewModel({
columns: [...]
initialSort: [...]
});
this.anotherTableViewModel = new AbstractTableViewModel({
columns: [...]
initialSort: [...]
});
};
My first try was mimicking the example [simpleGrid] plugin (# http://knockoutjs.com/examples/resources/knockout.simpleGrid.1.3.js) that the KnockoutJS docs use for the [Paged grid] example.
I'm not really sure, but I think that the basic concept of abstraction isn't represented well in this plugin. When I tried to include css class into the <th> elements like so: <th class="col col-id">, <th class="col col-name">, etc, I found out this wasn't easy (impossible?) using data-bind attributes.
The data-bind attributes probably shouldn't be used for this stuff through, because these classes won't change -- they are part of a higher abstraction level: we should actually insert these classes with jQuery.tmpl or Underscore's templating system. But then I got an error saying that [This template system doesn't support use of the foreach binding] (or something like that).
My second try therefore was to implement the abstraction as it should be implemented: with the table properties (columns, etc) at another "abstraction level" than the table data:
Create the basic <tr data-bind="foreach: rows"> html at instantiation of a new specific table view model, using an "abstract" template -- this I simply did with Underscore's _.template.
Let this specific viewmodel use the above html as usual.
In CoffeeScript:
do ->
ko.dataTable =
ViewModel: (config) ->
#id = config.id
#columns = config.columns
#pageSize = config.pageSize ? 9999
#sortColumn = ko.observable (config.sortColumn ? #columns[0].col)
#sortOrder = ko.observable (config.sortOrder ? "asc")
#data = ko.observableArray (config.data ? [])
null
ko.bindingHandlers.dataTable =
init: (el, acc) ->
viewModel = acc()
$(el).find("div:first").html dataTableTemplateMaker viewModel
# ??? [A] ko.applyBindings viewModel, $(el).find("table")[0]
# ??? [B] controlsDescendantBindings: yes
null
update: (el, acc) ->
viewModel = acc()
# ??? [C]
null
And then:
<div data-bind="dataTable: groupTable">
and:
class ViewModel
constructor: ->
#groupTable = new ko.dataTable.ViewModel
id: "grouptable"
columns: [
{ col: "num", title: "Groep", editable: yes }
{ col: "subject", title: "Vak" }
{ col: "year", title: "Jaar" }
{ col: "level", title: "Niveau" }
{ col: "day", title: "Dag" }
{ col: "hour", title: "Uur" }
{ col: "place", title: "Lokaal", editable: yes }
]
pageSize: 10
sortColumn: "num"
sortOrder: "asc"
data: [] # [D]
... in which ??? marks the spot(s) where my confusion lies.
Say I do not insert lines [A] and [B]. Then of course KnockoutJS tells me that the bindings are all messed up within the html for my specific viewmodel (which is inserted into the <div>. If I do insert lines [A] and [B], then it does work for initial data (at [D]), but after that does not respond.
Alltogether: I'm quite confused about something simple as abstracting a viewmodel. Isn't there a standard solution to this in KnockoutJS? (I've googled but couldn't quite find anything...) Or am I just messing it up myself (quite possible)? ;)
*UPDATE*
I solved the problem (but maybe it's not the best / well at all -- what is your opinion?), for completeness' sake: (a condensed version -- of course you'd probably also want to observe the rows individually etc..)
HTML (yes, that is purposefully a string passed to the binding handler)
<div data-bind="myTableBinding: 'viewModelPropertyHoldingTableViewModel'"></div>
CoffeeScript
class MainViewModel
constructor: ->
#viewModelPropertyHoldingTableViewModel = new TableViewModel <options>
null
class TableViewModel
constructor: (options) ->
#columns = options.columns
#rows = ko.observableArray (options.rows ? [])
[...]
null
tableTemplateMaker = _.template '
<table>
<thead>
<tr>
[% _.map(tableViewModel.columns, function (column) { %]
<th>[%= column.title %]</th>
[% } %]
</tr>
</thead>
<tbody data-bind="foreach: rows">
<tr>
[% _.map(tableViewModel.columns, function (column) { %]
<td data-bind="text: [%= column.id %]"></td>
[% } %]
</tr>
</tbody>
</table>
'
ko.bindingHandlers.myTableBinding =
init: (element, viewModelPropertyNameAccessor, _, mainViewModel) ->
tableViewModelProperty = viewModelPropertyNameAccessor()
tableViewModel = mainViewModel[tableViewModelProperty]
$(element).html tableTemplateMaker
tableViewModelProperty: tableViewModelProperty
tableViewModel: tableViewModel
null
m = new MainViewModel
ko.applyBindings m
m.viewModelPropertyHoldingTableViewModel.data.push [...]
Why reinvent the wheel? :P
https://github.com/CogShift/Knockout.Extensions
i'm using an AjaxSelect which contains several ids. By selecting an id the additional information to this id should be displayed in a given table which will be generated by a snippet. Now i want to know which would be the best solution to refresh my list?
HTML:
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr class="lift:MainScreen.cars">
<td><car:name /></td>
<td><car:type /></td>
</tr>
</tbody>
</table>
SCALA:
def doSelect(msg: NodeSeq) = {
SHtml.ajaxSelect(cars.map(i => (i.no.toString, i.no.toString + ". Car")),
Empty, {
selectedCar =>
controller.chooseCar(selectedCar.toInt)
// RELOAD TABLE
})
}
def cars(node: NodeSeq): NodeSeq = {
val cars = controller.chosenCarFamily.cars
cars match {
case null => Text("There is no items in db")
case game => game.flatMap(i =>
bind("car", node,
"name" -> car.name,
"type" -> car.type))
}
}
You should use ValueCell and WiringUI. Very good examples can be found at simple_wiring and invoice_wiring.
When using WiringUI, each time the valueCell cell is updated, the content linked with WiringUI.apply(cell) will be updated. So it should do the trick.
Here is an example for your specific case:
HTML:
Same as yours
SCALA:
class MainScreen{
def doSelect(msg: NodeSeq) // same as yours
def cars = WiringUI.apply(controller.chosenCarFamily)(displayResult)
def displayResult(carFamily:CarFamily)(node: NodeSeq) ={
carFamily.cars match {
case null => Text("There is no items in db")
case game => game.flatMap(i =>
bind("car", node,
"name" -> i.name,
"type" -> i.type))
}
}
}
object Controller{
val selectedCar = ValueCell(1)
def chooseCar = sectectedCar.set
val chosenCarFamily = selectedCar.lift(car:Int => //Stuff to output the family)
}