layout of 3 buttons together for 3 snippets

65 views
Skip to first unread message

Philippe Derome

unread,
Nov 29, 2015, 5:18:36 PM11/29/15
to Lift
I am trying to layout three buttons together horizontally on same page, but 3rd button (Cancel, always enabled) shows up on next row because of divs with one of the first two buttons always disabled. I am nesting 2 lift forms below. Is nesting two forms in html legitimate or not? 

I'd be happy not to nest the forms if that's wrong but have no familiarity yet with layout considerations in CSS and Lift to come up with a solution yet. I have one snippet associated with each of the three buttons using 2 distinct html files (one for Recommend and one for Consume). Putting the third Cancel enabled button in scope of outer form yields two enabled buttons and I cannot get any snippet to determine which of two buttons was clicked by user to apply proper functionality on the form (action is rewritten by Lift, so I don't think I can attempt to query form's action).

The Cancel button does more than coming back to home page by changing state shared across snippets.

<form data-lift="form.ajax"> <!-- Higher level form containing two buttons and data -->
<div data-lift=..... >
<div class="buttons">
<button id="Recommend" disabled><img src="/images/recommend.png" alt=""/>Recommend</button>
<button id="Consume"><img src="/images/winesmall.png" alt=""/>Consume</button>
<form data-lift="form.ajax">
<div data-lift="Cancel?form=post">
<button name="Cancel"></button>
</div>
</form>
</div>
</div>  <!-- end of higher level div-->
</form> <!-- end of higher level form -->

object Cancel {
def render =
"@Cancel" #> SHtml.ajaxButton("Cancel", () => {
ProductSelectionCache.product.set(None) // clear out data using a SessionVar Box.
RedirectTo("/index")
}) &
"@Cancel -*" #> <img alt="" src="/images/cancel.png"></img> // make sure to add content with image
}

Brett Grace

unread,
Nov 29, 2015, 9:34:47 PM11/29/15
to Lift

On Sunday, November 29, 2015 at 2:18:36 PM UTC-8, Philippe Derome wrote:
I am trying to layout three buttons together horizontally on same page, but 3rd button (Cancel, always enabled) shows up on next row because of divs with one of the first two buttons always disabled. I am nesting 2 lift forms below. Is nesting two forms in html legitimate or not? 

Nested forms do not conform to HTML5 but browsers are pretty good at handling non-conforming HTML and making it work somehow. It will probably work flawlessly 60% of the time. Personally, I would try to avoid it.

The break in layout is probably caused by the div, as you observe, but note that there's no requirement for the data-lift attribute to be in a div. You can add data-lift to any element, for example span if you want to get inline flow.

Lastly, I don't understand if you need something form-like with the Cancel button or if that's just what you tried to make it work. You should be able to do the same thing without wrapping the button in a form. There are a variety of techniques, many covered here: http://chimera.labs.oreilly.com/books/1234000000030/ch05.html#_solution_43 (actually one of the recipes covers including a button which performs a redirect). I actually wouldn't be surprised if your code continued to work if you remove the inner form which surrounds the cancel button.
 
Good luck!

Philippe Derome

unread,
Nov 29, 2015, 10:10:00 PM11/29/15
to lif...@googlegroups.com
I'll follow up tomorrow. I didn't think of associating data-lift with non div elements. That does it! I never doubted Liftweb is very flexible but it takes time trying out things to start to see it and this example is very positive on that. I'll read the other material later so that I have a solution that does not mess up form hierarchy.

Thanks so much.

By the way, to me this issue is tied to the other topic we just discussed but I'd have to share code to make that apparent.

--
--
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 a topic in the Google Groups "Lift" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/liftweb/JWNT6J0A2_M/unsubscribe.
To unsubscribe from this group and all its topics, send an email to liftweb+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Farmer

unread,
Nov 30, 2015, 9:14:11 AM11/30/15
to Lift
Regarding this line

Nested forms do not conform to HTML5 but browsers are pretty good at handling non-conforming HTML and making it work somehow. It will probably work flawlessly 60% of the time. Personally, I would try to avoid it.

It’s worth pointing out that Lift uses a Strict HTML5 parser to parse the page for executing snippets. As a result, things that would work in a browser can cause unexpected results in Lift’s parser. So if you feed in non-conformant HTML5 into Lift you may not get the same structure back.

FYI.


Matt Farmer | Blog | Twitter
GPG: CD57 2E26 F60C 0A61 E6D8  FC72 4493 8917 D667 4D07

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.

Philippe Derome

unread,
Nov 30, 2015, 8:16:24 PM11/30/15
to Lift
thanks Matt. I'll research further and avoid such form nesting.

Antonio Salazar Cardozo

unread,
Nov 30, 2015, 9:03:01 PM11/30/15
to Lift
Provided that the browser parses HTML5 correctly (most modern browsers do), Lift's parsing
and the browser's parsing should be identical (assuming your doctype is an HTML5 doctype).
This is because HTML5 has a defined algorithm for dealing with invalid nestings.

It's likely that modern browsers would close the previous form element and start a new nesting
context if you tried to nest form elements (provided an HTML5 doctype), and thus it's likely Lift
would do the same.
Thanks,
Antonio

On Monday, November 30, 2015 at 9:14:11 AM UTC-5, Matt Farmer wrote:
To unsubscribe from this group and all its topics, send an email to liftweb+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

-- 
-- 
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+unsubscribe@googlegroups.com.

Philippe Derome

unread,
Dec 5, 2015, 11:11:34 PM12/5/15
to Lift
Matt, Antonio, thanks, I stayed away from nesting forms. I also applied Brett's advice in pulling the buttons out of html forms as there were no compelling reasons to keep them in forms and then associated the lift components with span html elements instead of div and I got the three buttons in horizontal layout as I wished. Separating a pair of buttons out of a single form bound to a snippet made the whole organization of the page and code much more rational and flexible.
To unsubscribe from this group and all its topics, send an email to liftweb+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

-- 
-- 
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.

Antonio Salazar Cardozo

unread,
Dec 6, 2015, 2:28:58 PM12/6/15
to Lift
Awesome! Good to hear you zeroed in on a design that feels right :)
Thanks,
Antonio

Philippe Derome

unread,
Dec 20, 2015, 10:05:33 PM12/20/15
to Lift
And I recently applied a suggestion from D. Pollak on this board on how to obtain the input element id on the server side that he had submitted to another newbie a few years ago, using ajaxCall and JsRaw("this.id"). This allows me to handle a group of 3 buttons together and identify in one single snippet which one got clicked. In below, there are 3 buttons with html-id consume, recommend, and cancel respectively, and I toggle disabled=false or disabled=disabled between consume and recommend.
"* [onclick]" #> SHtml.ajaxCall(JsRaw("this.id"), { (s: String) =>
s match {
case "consume" => consume()
case "recommend" => recommend()
case "cancel" => cancel()
case _ => Noop
}

Antonio Salazar Cardozo

unread,
Dec 21, 2015, 4:22:51 PM12/21/15
to Lift
This sounds strongly like you're trying to do the work of a radio
button with a regular button—a radio button might be a better
way to go. Another approach is to give each button the same
`name` but a different `value`, which will then be submitted as
the string parameter if you use `this.value` instead of `this.id`.
I prefer not to use ids for submission values when `value` is
there for exactly that semantic purpose :)
Thanks,
Antonio

Philippe Derome

unread,
Dec 21, 2015, 11:10:26 PM12/21/15
to lif...@googlegroups.com
Thanks for all the help. Any response in 2016 is fine with me now... Merry Christmas to the volunteers!

yes, conceptually, it's like a radio button with pictures of regular buttons. So can I attach text and image as an element of the radio button? If so, that's what I want, just I haven't seen that done yet. Is that normal html5?

I just tried out value suggestion in place of id for onclick ajaxCall handler and that works well.

The essence of what I am trying to do here is as follows: a 2-3 buttons Ajax and as result of user button presses send immediate feedback to user and toggle the first 2 buttons as to which one is enabled (so the pair of buttons meets the criterion of radio usage). 

// applies to 3 buttons in a span 
"* [onclick]" #> SHtml.ajaxCall(JsRaw("this.value"), { (s: String) => ... test s in match and call appropriate JsCmd callback at end of which some JS gets executed to update browser elements.

In recent weeks, I was using comet actors to display results back to page only to find out that different sessions were receiving the same feedback, which I had meant private to the session.

I am now leaning towards doing a fair bit of JS at end of http request cycle so that the updating is immediate. And that may mean learning some of the rich Js abstraction that exists after I do an initial cut with JsRaw.


Philippe Derome

unread,
Dec 22, 2015, 11:55:37 AM12/22/15
to lif...@googlegroups.com

Consider this inquiry as closed. Thanks for the good teaching.

First questions about images as choices for radio are generally answered on web using CSS/CSS3 techniques such as at stackoverflow.com, well outside of lift's scope.

Antonio Salazar Cardozo

unread,
Dec 23, 2015, 8:46:31 PM12/23/15
to Lift
Awesome… Out of curiosity, what did your final solution look like (at least
in broad strokes)?
Thanks,
Antonio
To unsubscribe from this group and all its topics, send an email to liftweb+unsubscribe@googlegroups.com.

Philippe Derome

unread,
Dec 23, 2015, 9:49:34 PM12/23/15
to lif...@googlegroups.com
I still work only outside of my day job and am still slow at lift development after about 2 months of starting to look at it, so I am still working with this current issue. This link provides potential guidance for using a radio with images and presumably to do it horizontally: http://stackoverflow.com/questions/17541614/use-image-instead-of-radio-button

For now, I have buttons but working towards making them radio elements for the first two alternatives that are mutually exclusive (as per your suggestion), the third one would be independent button (I call it cancel, others might call it reset). The first button fetches a random product from a catalogue using that catalogue's Rest API while the second one simulates a transaction for confirming selecting of product by incrementing a user's count in a Postgres database (for now using Mapper, but I plan to try out Squeryl-Record) and echoing back what that count is for that catalogue product. The buttons have some control to avoid maniac repeated consecutive clicks for which I want to capture only the first event. It's all a device to introduce me to web, lift, FP, Scala, and JS. 

My 3 buttons are Ajax because I wanted RIA in browser. And I wanted to avoid any call to RedirectTo when done. In past few weeks I had tried comet and comet actors, which apparently was great for the feedback that was asynchronous, but then I eventually found out that distinct users would get the same feedback, while the feedback was meant to be for a single session. I chose not to pursue named comets as per Diego Medina alias fmpwizard and went back to snippets but now using the JsCmd to push the changes to browser (now that cometactor reRender is not available); there's no reason to use comets in my use case I think. I also rejected the idea of idMemoize as being a little difficult/unnatural for task at hand. So I had a fair bit of NodeSeq CSS transformers that got taken out of code in (bad) comet usage and were replaced by lift JsCalls of various kinds that achieved the same (including writing <li></li> items ), for example:
def prodAttributesJS(p: Product) = {
  val nodeSeq = for (x <- p.createProductLIElemVals) yield <li>{x}</li>
SetHtml("prodAttributes", nodeSeq)
}

So I have quite a few vals/defs that are JsCmds and I combine them with & at end of callback and it works well on the surface of things, in fact I was surprised how easy it was to use. I did see some scary/nefarious Unmapped Lift-like parameter seen in request [/]: F1248216611878RCLVMO you are familiar with when reverting from comets to snippets and indeed the actions don't get triggered (it seems to be related to combining distinct users logging in/out in same browser and users somehow jumping from one tab to the other tab, but I think I have some issues to look at in my JS commands; I have no reason to believe it's any issue with lift itself as I didn't start investigation). Some classic easy-lift reference (not dpp one but a collective of 3 guys) says not to abuse JsCmds/JsExps abstraction (JavaScript generated on server side) and instead use JE.Call to some code on browser. I suppose that they are suggesting that JavaScript interpreter for natural native code in browser is more efficient or simpler than using Lift's abstraction for JS.


Antonio
To unsubscribe from this group and all its topics, send an email to liftweb+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
--
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 a topic in the Google Groups "Lift" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/liftweb/JWNT6J0A2_M/unsubscribe.
To unsubscribe from this group and all its topics, send an email to liftweb+u...@googlegroups.com.

Philippe Derome

unread,
Dec 24, 2015, 5:02:31 PM12/24/15
to lif...@googlegroups.com
Looks fairly nice now. The radio ChoiceHolder holds usual label strings but also an img element (NodeSeq) via case Class RadioElements. When doing toForm I expand the radio elements to include the img within htmlize and the final step of render hides the common radio circular radio button.

What is missing is special effects to provide feedback of one of three buttons being pressed (darker or with a frame or something alike).

The NodeSeq transformation on .options selector selects callback by html element name as you suggested, but for my bookkeeping or state holding I track only changes of the first two buttons by setting a http session variable. In code not shown here, I send error message with S.error for clicking twice in a row on one of the first two buttons and I clear out the error when the user acts as expected. The LabelStyle idea to generalize radio buttons, I found on the web about 4-6 weeks ago; the guy was using Twitter Bootstrap using that.

I don't know whether I could do this more easily using CSS techniques within my static template page instead and avoid the explicit input [hidden] at end of render. I'll explore that later on.
case class RadioElements(name: String, img: NodeSeq) {}

object LabelStyle {
def htmlize(item: ChoiceItem[RadioElements]): NodeSeq = {
val ns: NodeSeq = item.xhtml ++ item.key.img
<label class="radio">
{ns}{item.key.name}
</label>
}

def toForm(holder: ChoiceHolder[RadioElements]): NodeSeq = {
holder.items.flatMap(htmlize)
}
}
End of snippet's render method uses above as follows:
".options" #> LabelStyle.toForm(SHtml.ajaxRadio(
RadioElements("recommend", <img src="/images/recommend.png" alt="recommend: question mark"/>) ::
RadioElements("consume", <img src="/images/winesmall.png" alt="consume: glass of wine"/>) ::
RadioElements("cancel", <img src="/images/cancel.png" alt="cancel: X"/>) ::
Nil,
Full(RadioElements(theRecommendRadioItem.is, <p></p>)), // the <p></p> is a hack.
(s: RadioElements) => {
s.name match {
case "consume" =>
theRecommendRadioItem.set(s.name)
consume()
case "recommend" =>
theRecommendRadioItem.set(s.name)

recommend()
case "cancel" => cancel()
      case _ => Noop // for safety
}
})) andThen
"input [hidden]" #> "true" // to hide the classic circle of the radio button (needs to be scheduled after prior NodeSeq transformation
Reply all
Reply to author
Forward
0 new messages