Created
February 27, 2012 06:54
-
-
Save nafg/1922051 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package reactive | |
package web | |
package widgets | |
import scala.xml.NodeSeq | |
import net.liftweb.util.{ CssSel, Helpers, PassThru } | |
import Helpers._ | |
abstract class TableFormlet[A](val signal: SeqSignal[A]) extends Formlet[Seq[A], NodeSeq => NodeSeq, Nothing] { | |
def columns: A => Seq[Formlet[Any, CssSel, Any]] | |
class RowFormlet(item: A) extends Formlet[A, CssSel, Nothing] { val signal = Val(item) } | |
val rows = signal.now.map(new RowFormlet(_)).signal | |
override def rendering = | |
Repeater(rows.now.map(_.rendering.foldLeft("thisbetternotexist" #> "")(_ & _)).signal) :: | |
Nil | |
} | |
/** | |
* Extensible trait to display tabular data | |
*/ | |
trait TableView[A] { | |
/** | |
* The type of modifiers to `fetch`. For instance, | |
* if you use `lift-mapper` this may be `Seq[QueryParam[MyModelType]]`. | |
* If you use ScalaQuery then this may be `Query[MyModelType]=>Query[MyModelType`. | |
*/ | |
type FetchParam | |
/** | |
* The items will be refreshed when the element | |
* specified by this selector is clicked. | |
*/ | |
def refreshSelector = ".refresh" | |
/** | |
* The items will be rendered in `Repeater` | |
* in the element specified by this selector. | |
*/ | |
def repeaterSelector = ".items" | |
/** | |
* A column -- binds one value of a row | |
*/ | |
trait Col { | |
/** | |
* The css selector to render this column inside of | |
*/ | |
def selector: String | |
/** | |
* Render the actual value for a given row | |
*/ | |
val renderer: RowType => NodeSeq => NodeSeq | |
/** | |
* The css selector expression that renders this column. | |
*/ | |
def render(row: RowType): CssSel = selector #> renderer(row) | |
override def toString = "Col{"+selector+"}" | |
} | |
/** | |
* Creates an column with the given selector and renderer function | |
*/ | |
def Col(selector0: String, renderer0: RowType => NodeSeq => NodeSeq): Col = | |
new Col { | |
val selector = selector0 | |
val renderer = renderer0 | |
} | |
/** | |
* A row -- wraps an item and can render it. | |
* You may want to subclass `Row` and set `RowType` to your subclass. | |
*/ | |
trait Row { this: RowType => | |
/** | |
* The item currently in this row (for immutable items, this may | |
* point to different items over time). | |
*/ | |
def item: A | |
def render: CssSel = cols map (_.render(this)) reduceLeft (_ & _) | |
} | |
/** | |
* The actual subtype of Row used | |
*/ | |
type RowType <: Row | |
implicit def observing: Observing | |
/** | |
* The columns | |
*/ | |
def cols: List[Col] | |
protected lazy val params: Signal[List[FetchParam]] = Val(Nil) | |
/** | |
* Get the items from the data source | |
* @param params Data that parameterizes the fetch, such as query parameters. | |
*/ | |
def fetch(params: Seq[FetchParam]): Seq[RowType] | |
/** | |
* Events from this event stream cause `initial` to be reset to `fetch`. | |
* Thus if the data in the data source changed, | |
* firing an event from `refreshes` will | |
* cause the new data to be loaded. | |
*/ | |
val refreshes = new EventSource[Unit] | |
protected lazy val initialImpl = for { | |
_ <- refreshes hold () | |
filters <- params | |
} yield fetch(filters) | |
/** | |
* The set of `Row`s as they are in the data source. | |
* In-memory modifications, such as filters or edits, may be | |
* applied to `initial` to yield `rows`. They will be reapplied | |
* when `initial` changes. | |
* | |
* If you have a data source that notifies of changes (e.g., CouchDB), | |
* you may want to override initial to take those notifications into | |
* account. | |
*/ | |
def initial: Signal[Seq[RowType]] = initialImpl | |
/** | |
* Fire a unit event from `refreshes`. | |
* Override to prevent refreshing in certain circumstances | |
* (for instance `TableEditor` prompts if there are | |
* unsaved changes). | |
*/ | |
protected def fireRefresh(): Unit = refreshes fire () | |
/** | |
* Wrap an item in a `Row` | |
*/ | |
def Row(item: A): RowType | |
/** | |
* Modify the rows displayed relative to those loaded (`initial`). | |
* By default the `identity` function (no change). | |
*/ | |
def filterRows: Signal[Seq[RowType]] => Signal[Seq[RowType]] = identity | |
private lazy val rowsImpl = SeqSignal(filterRows(initial)) | |
/** | |
* The rows to display | |
*/ | |
def rows: SeqSignal[RowType] = rowsImpl | |
def render = | |
refreshSelector #> onServer[Click]{ _ => | |
fireRefresh() | |
} & | |
repeaterSelector #> Repeater { | |
rows.now.map(_.render).signal | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment