map collection to multiline table row

39 views
Skip to first unread message

ari gold

unread,
Mar 2, 2018, 9:03:33 PM3/2/18
to Lift
Hello there good people & happy anniversary :)

This might just be a case of late-on-a-friday buuuuut:

I have a table where each 'row' is actually two rows, a primary row and a secondary row. Here's some asciiart:

|------------------------------------------------------|
|          |   id     | bass     | treble   |  beat    |
|  name    |----------|----------|----------|----------|
|          |  genre   | artist   | instr    |  score   |
|------------------------------------------------------|


Right now I have that as two <tr>s where the first <td> has rowspan=2.

I'm wondering how to map a collection across both rows. Because, of course,

".primary-row" #> musicStuff.map { ... } &
".secondary-row" #> musicStuff.map { ... }


won't work. It'll first map over the primary rows and then map over the secondary rows (duh).

Is it possible to "zip" the mapping? Something like

".primary-row .secondary-row" #> musicStuff.map { ... }

Which would, in my fantasy syntax, combine the two NodeSeqs so that they could be treated as a single element.

Incidentally I'm looking at a solution that uses only one row but that.. well it feels semantically yucky.

Hmm...thanks!

Ari

Antonio Salazar Cardozo

unread,
Mar 3, 2018, 1:21:27 PM3/3/18
to Lift
I think you'll need a container for that, unfortunately. You might be able to do something
dirty like this:

def snippetMethod(ns: NodeSeq): NodeSeq = {
  var primaryTemplate: NodeSeq = _
  var secondaryTemplate: NodeSeq = _
  (".primary-row" #> { row: NodeSeq => primaryTemplate = row; NodeSeq.Empty } &
   ".secondary-row" #> { row: NodeSeq => secondaryTemplate = row; NodeSeq.Empty }).apply(ns)

  "table *" #> musicStuff.map { ...
    Seq(
      (".primary-row" #> ...).apply(primaryTemplate),
      (".secondary-row" #> ...).apply(secondaryTemplate)
    )
  }
}

Preeeeeeeetty gnarly. If you know the primary and secondary row are the only children
of, say, the table, you can also:

"table *" #> { ns: NodeSeq => ns match {
  case table: Node if table.name == "table" =>
    val rows = table.child

    musicStuff.map { …
      (".primary-row" #> ... &
       ".secondary-row" #> ...).apply(rows)
    }
  case other => other
} }

Little less annoying, significantly safer, still nasty. CSS selector transforms are pretty
specifically designed to deal with single elements at a time, unfortunately.
Thanks,
Antonio

sebastian....@gmail.com

unread,
Mar 6, 2018, 8:26:55 AM3/6/18
to Lift
Maybe i am getting this wrong.
So you are splitting your data?

Lets say your data looks like

val musicData: List[List[String]  // where every row is a list of lists for each main row and its data.

then i would try something like:

def render =
 "table" #> musicData.map{ row =>
   "tr .primary" #> row.head &
   "tr .secondary" #> row.tail.map { d => "td" #> d  }
}

If you did something like this why change it? Does not seem so bad to me :D

Best regards

Antonio Salazar Cardozo

unread,
Mar 6, 2018, 12:45:29 PM3/6/18
to Lift
Well that particular example will create a table for each entry :) But could be that "table *" might work,
if the only thing in the table are those two rows.
Thanks,
Antonio

ari gold

unread,
Mar 7, 2018, 2:04:51 PM3/7/18
to Lift
Here's an update from the trenches.

I tested a bit, and it doesn't seem like I can use table * because of the thead. That is, the trs aren't the only children of the table:

<table>
  <thead></thead>
  <tbody>
    <tr class="primary-row">
    <tr class="secondary-row">
  </tbody>
</table>

I tried using an id on the tbody which gets close but not quite, I think because the id of the tbody gets lost -- maybe 'cause all this is wrapped in an idMemoize so that the form can update the table?

Also looking into different structures for the table so that I could match on table * as per Antonio's original idea.

I'll report back when I find out more :)

Thanks a bunch!

Ari

ari gold

unread,
Mar 9, 2018, 9:18:20 PM3/9/18
to Lift
Back at it and thought I'd post another update.

[NB. I got past the issue with an id on tbody. I'm not sure what was happening there (tho I read that id's get stripped) but it doesn't seem to be an issue anymore -- I can just use "tbody".]

So I've got

"tbody *" #> { ns: NodeSeq => ns match {
  case table: Node if true => // for now I'm just using "true" to get things working
    val rows = table.child

    musicStuff.map { …
      (".primary-row" #> ... &
       ".secondary-row" #> ...).apply(rows)
    }
  case other => other
} }

but this is giving me a whooooole bunch of tbodys. After thinking about it, it seems like this (essentially Antonio's original suggestion) is doing "tbody *" #> musicStuff.map which will always create a bunch of tbodys.

Probably be heading into Knockout-land, which is what we used for our other multi-row table. Tho I might also use something other than a table so i can have container for those two trs, tho yuck.

Last little thought -- I feel like being able to apply a collection to a group of elements could be real handy. Real handy. 🤔

Thanks & all the best to y'all,

Ari

PS. pardon the stream-of-thoughtedness of this post. I edited but still feels ~.

On Saturday, March 3, 2018 at 10:21:27 AM UTC-8, Antonio Salazar Cardozo wrote:

sebastian....@gmail.com

unread,
Mar 12, 2018, 11:01:30 AM3/12/18
to Lift
Jeah having * or not does not always behave intuitively :)

Html tables in general are somewhat painful. I just found that you need to have 1n additional element alongside the rowspan2 element for it to even work.

But have you thought about putting a second table in your first and not using rowspan at all?
You will get exactly what you want and rendering the stuff is pretty easy:

The markup then looks like:


 <div class="row" data-lift="TestThing">

       
<table class="table">
           
<tr>
               
<td class="double-span">asdf</td>
               
<td>
                   
<table class="table">
                       
<tr><td class="first-inner">asdf</td><tr>
                       
<tr><td class="second-inner">asdf</td></tr>
                   
</table>
               
</td>
           
</tr>
       
</table>
   
</div>


And your snippet:

class TestThing {
  val rowData
=  List("name", "id", "bass", "treble", "beat", "genre", "artist", "instr", "score")

  val data
= (0 to 10).map( i => rowData.map(_ + i))

 
def render = ".table tr" #> data.map { row =>
    val innerTableData
= row.tail.splitAt(4)
   
".double-span *" #> row.head &
   
".first-inner *" #> innerTableData._1 &
   
".second-inner *" #> innerTableData._2

 
}
}


Unless you need your markup to be a single table using rowspan=2 this should work nicely :)
Of course this does not solve the problem of rendering groups of things in one go.
I personally never needed this and if you want to you could give every html element a css class and use selectors in your snipet to fill them with data.

Best regards
Reply all
Reply to author
Forward
0 new messages