CssBoundLiftScreen with tabbed content

94 views
Skip to first unread message

AGYNAMIX Torsten Uhlmann

unread,
Oct 18, 2012, 10:35:47 AM10/18/12
to lif...@googlegroups.com
Me again,

I've created a somewhat lengthy screen and would like to use Bootstrap's Tabs (http://twitter.github.com/bootstrap/javascript.html#tabs)
to make it more appealing- basically I would need several sections of "fields" definitions and in the LiftScreen I would somehow tell in which section
the field should be generated in.

Is something like that possible and how would I go along to implement it?

Thanks,
Torsten.

Tim Nelson

unread,
Oct 18, 2012, 12:12:26 PM10/18/12
to lif...@googlegroups.com
Hi Torsten,

I think it would be nice that instead of LiftScreen having a single list of fields, it would have a list of field lists, so that you could surround each list of fields with custom html, ie the fieldset tag. This would also allow you to put each fieldset in a tab.

Something along these lines:

case class Fieldset(id: String, fields: List[BaseField]) // it might be nice to be able to identify a specific fieldset, hence the id.

In LiftScreen,scala:

def screenFields: List[Fieldset] = ...


Tim

AGYNAMIX Torsten Uhlmann

unread,
Oct 18, 2012, 12:14:28 PM10/18/12
to lif...@googlegroups.com
I found a solution. Basically I'm identifying each field I want on the screen and put it into the appropriate div:

<ul class="nav nav-tabs">
  <li class="active"><a href="#person" data-toggle="tab">Person</a></li>
  <li><a href="#address" data-toggle="tab">Adresse</a></li>
</ul>
<div class="fields">
  <div class="tab-content">
    <div class="tab-pane active" id="person">
      <div id="memberForm_number_field"></div>
      ...
    </div>
    <div class="tab-pane" id="address">
      <div id="memberForm_street_field"></div>
    </div>

In the LiftScreen I put a "FieldBinding("field_name")" into the field definition. That works but is a bit verbose.
Is there a better way?

Thanks,
Torsten.

-- 
AGYNAMIX(R). Passionate Software.
Inh. Torsten Uhlmann | Buchenweg 5 | 09380 Thalheim
Phone:       +49 3721 273445
Fax:             +49 3721 273446
Mobile:       +49 151 12412427
Web:           http://www.agynamix.de

Peter Brant

unread,
Oct 18, 2012, 4:43:07 PM10/18/12
to lif...@googlegroups.com
Yeah, that's what I was going to suggest.  The FieldBinding(...) parameters are the price you pay for using a custom layout.  The embedded template is evaluated eagerly though so you could have another snippet produce the tab layout.  That could at least reduce the amount of boilerplate in the HTML.

Pete

--
--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code
 
 
 

Naftoli Gugenheim

unread,
Oct 18, 2012, 8:47:40 PM10/18/12
to lif...@googlegroups.com
Maybe there needs to be a "Field" that is a composite of other fields.
Not familiar with LiftScreen enough to know if that makes sense with the current design, but that would be a good design (although it may make "Field" be a poor name).
Then again, once you do that, the Screen itself can be a Field. Almost like formlets.


AGYNAMIX Torsten Uhlmann

unread,
Oct 19, 2012, 2:18:31 AM10/19/12
to lif...@googlegroups.com
Thanks for the tip, Peter!

I have these field to underlying field mappings anyway in order to get the errors displayed nicely with the Bootstrap layout. I try to merge the FieldBinding into that existing function. Here's what it looks like now (just for the record):

val number: Field{type ValueType = Int} = field(screenVar.is.number, flagFieldError(number, "input-mini"), FieldBinding("number"))

Here's the flagFieldError method:

def flagFieldError(field: => Field, addCls: String*): FieldTransform = {
  FieldTransform(() => {
    (if (hasErrors(field)) {".fieldContainer [class+]" #> "error"} else {"#NotExistent" #> ""}) &
    (if (addCls.size > 0) {
      ".value [class+]" #> addCls.mkString(" ")
    } else {
      "#NotExistent" #> ""
    })
  })
}

def hasErrors(field: Field) = S.errors.exists(t => t._2.isDefined && t._2 == field.uniqueFieldId)

Thanks for your support!
Torsten.

AGYNAMIX Torsten Uhlmann

unread,
Oct 19, 2012, 2:30:51 AM10/19/12
to lif...@googlegroups.com
Hi Naftoli & Tim,

that sounds like a good idea to me. It's in principle the same as Tim suggested for one level nesting, but you save the extra element (a list), additionally you can nest several levels.

I though have no idea how that could be implemented and if it's possible as all :)

Torsten.

Peter Brant

unread,
Oct 20, 2012, 6:53:49 PM10/20/12
to lif...@googlegroups.com
This isn't quite as slick as what Naftoli describes, but you can put your fields in some other container if it makes it easier to manage.

e.g. you can have something like

class Tab(fields: Field*, ...) {
  def template: NodeSeq = ...
}

val tabs = List(
  Tab(field(...), field(...)),
  Tab(field(...), field(...), field(...))
)

def renderTabs: NodeSeq = tabs flatMap (_.template)

in your form.

Pete

AGYNAMIX Torsten Uhlmann

unread,
Oct 21, 2012, 3:19:09 AM10/21/12
to lif...@googlegroups.com
I solved it similarly:

I created a method "tfield" on top of the "field" method that takes an additional name of a tab the field is associated with. If there is none the tab is called "default" and the tab control is not rendered. In addition that field method can now do my FieldBinding and also the FieldTransform I need for the errors to be properly shown in the Bootstrap markup

The tfield method adds my field to a list of tab->field associations which I use later on to render the markup.

Then I override the defaultXml() method of the LiftScreen, injecting the markup from some central place and do some processing of the markup, specifically rendering the bindings to the fields as shown in your "CustomBindingLiftScreen".

Now a binding for some of the fields looks like this:

  tfield("Person", "input-mini", screenVar.is.number)
  tfield("Person", screenVar.is.lastname)
  tfield("Address", screenVar.is.street)
  tfield("Address", screenVar.is.city)

(I added a variation to tfield to be able to pass custom css classes to the field form.)

That's what tfield looks like:

protected def tfield[T](tabName: String, cssCls: String, underlying: => BaseField {type ValueType = T},
                         stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): Field {type ValueType = T} = {
  val addedStuff: List[FilterOrValidate[T]] = FieldBinding(underlying.name) :: stuff.toList
  lazy val f: Field {type ValueType = T} = field[T](underlying, (flagFieldError(f, cssCls) :: addedStuff):_*)(man)

  // add field to screenTabs list

  f
}

The field has to be created with lazy val in order to use the value recursively inside the flagFieldError method, otherwise the compiler complains.

That approach seems to work reasonable well, and it's based on all your cool suggestions :)

Thanks,
Torsten.

alexmnyc

unread,
Mar 29, 2013, 12:34:33 PM3/29/13
to lif...@googlegroups.com
It would be greatly helpful if you could post some gist or a complete example of how you use it with Record or Mapper - I'm trying to implement CssBoundScreen against Record/bootstrap set up and I'm running into some issues during the lack of documentation.

Peter Brant

unread,
Mar 30, 2013, 7:11:23 AM3/30/13
to lif...@googlegroups.com
We don't use Record so I don't have a complete example handy, but I think the basic approach is to mirror the Record fields into your screen so you can apply e.g. FieldBinding, etc. that only work in the screen (e.g. something like

object MyScreen {
  object myModel extends RequestVar[MyModel](...)

  val myModelField = field(
    myModel.myModelField,
    FieldBinding(...),
    FieldTransform(...)
  )
}

Pete




On Fri, Mar 29, 2013 at 5:34 PM, alexmnyc <a.mik...@gmail.com> wrote:
It would be greatly helpful if you could post some gist or a complete example of how you use it with Record or Mapper - I'm trying to implement CssBoundScreen against Record/bootstrap set up and I'm running into some issues during the lack of documentation.

--
--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code
 
---
You received this message because you are subscribed to the Google Groups "Lift" group.
To unsubscribe from this group and stop receiving emails from it, send an email to liftweb+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

alexmnyc

unread,
Mar 31, 2013, 6:24:12 PM3/31/13
to lif...@googlegroups.com
Peter, thank you.
 
I can't seem to figure out a way to rebind fields to the newly set ScreenVar. With the regular LiftScreen, there is addFields method that gets called and resets the bindings but if I use
 
addFields(() => new FieldContainer {
      def allFields = ScreenVar.is.allFields}
)
 
I can't get fields to show up with CssBoundLiftScreen and I cannot think of a way to add FieldBinding dynamically either. It seems that upon screen reload this is the only callback where such rebinding could occur. I need to be able to re-use the screen and load different values in the fields based on the newly set SCreenVar and if I instantiate fields inline they never get rebound to the newly set screenvar.
 
Do you have any suggestion on how to achieve reusable field bindings against the model in CssBoundLiftScreen?
 
Thank you.

alexmnyc

unread,
Mar 31, 2013, 7:14:43 PM3/31/13
to lif...@googlegroups.com
I also tried this:
 
addFields(() => new FieldContainer {
      def allFields =  Venue.createRecord.allFields.flatMap(f => field (f,FieldBinding(f.name)))
})
 
but keep getting
 
scala:X: type mismatch;
[error]  found   : net.liftweb.record.Field[_$7,com.project.model.V
enue] where type _$7
[error]  required: net.liftweb.util.BaseField{type ValueType = this.ValueType}
[error]       def allFields =  Venue.createRecord.allFields.flatMap(f => field (
f,FieldBinding(f.name)))
[error]
^
[error] one error found

Torsten Uhlmann

unread,
Apr 1, 2013, 2:42:03 AM4/1/13
to lif...@googlegroups.com
In the constructor of your CssBound derived LiftScreen class, try to use the "field" method of trait "Field" in "AbstractScreen.scala" to add the fields individually.
I'm doing this because I add FieldTransforms to the field and that is (was?) only possible when adding the fields individually.  But it might just be the way to get you started:

In the constructor:

- field(Venue.theFieldName)

Does that help?

Torsten.

-- 
AGYNAMIX(R). Passionate Software.
Inh. Torsten Uhlmann | Buchenweg 5 | 09380 Thalheim
Phone:       +49 3721 273445
Fax:             +49 3721 273446
Mobile:       +49 151 12412427
Web:           http://www.agynamix.de

Alex M

unread,
Apr 1, 2013, 2:49:46 AM4/1/13
to lif...@googlegroups.com
Unfortunately not. When I use field(model.fieldname) in the constructor, when the screen is being reused for a different model the old values stay the same for some reason.

For example, I have three records:

Name   Action

Story1    Edit  Delete
Story2    Edit  Delete


When I the user clicks Edit, I set ScreenVar of the Edit CSS bound screen and the correct name shows, but when I go back to the list and click on the second Edit, still the old value of "Story 1" is present with that approach.

Since ScreenVar is being re-set with another record has probably something to do with it.




You received this message because you are subscribed to a topic in the Google Groups "Lift" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/liftweb/efAJqC3ZKfs/unsubscribe?hl=en-US.
To unsubscribe from this group and all its topics, send an email to liftweb+u...@googlegroups.com.

Torsten Uhlmann

unread,
Apr 1, 2013, 3:07:06 AM4/1/13
to lif...@googlegroups.com
Hm, ok.

Maybe that helps: In my app I'm using a LiftScreen on the page and a table on top from which I select records to be displayed in the LiftScreen. When a record is selected the same LiftScreen instance has to display new values. I pass it a new instance of the Record type and ask it to rerender.


Does that help you?

Torsten.

-- 
AGYNAMIX(R). Passionate Software.
Inh. Torsten Uhlmann | Buchenweg 5 | 09380 Thalheim
Phone:       +49 3721 273445
Fax:             +49 3721 273446
Mobile:       +49 151 12412427
Web:           http://www.agynamix.de

Peter Brant

unread,
Apr 1, 2013, 5:27:12 PM4/1/13
to lif...@googlegroups.com
Definitely consider throwing up a sample app too.  I think we're all really just guessing as to the specifics of what you're trying to do (at least I know I am).

Pete


Torsten Uhlmann

unread,
Apr 2, 2013, 2:12:56 AM4/2/13
to lif...@googlegroups.com
Yep, I also don't have a clear picture of the problem. With other words, I'm just guessing :)

Torsten.
Reply all
Reply to author
Forward
0 new messages