SHtml.textarea as a CssSel - lift

Is there any way to create a CssSel that has a SHtml.textarea/SHtml.text/SHtml.input/SHtml.button logic?
What I want is, having a NodeSeq
<textarea class="dontDeletePlease"></textarea>
and a CssSel like
"*" #> SHtml.textarea(...)
have a result
<textarea class="dontDeletePlease" onsubmit="..." name="..."></textarea>
But, instead, I'll get the class "dontDeletePlease" erased. And all other attributes too.
Is there any way to create a CssSel that would apply it's logic keeping the html attributes?

I think you have two options. You can use Lift's funcMap to add the function directly to your existing textarea. This will not modify anything except to add a name attribute directly to your tag. This is similar to what Lift does internally with SHtml.textarea.
"*" #> {
fmapFunc(SFuncHolder((t) => //do something))(funcName =>
"* [name]" #> funcName
)
}
The other is to grab the attributes from within the CSS Selector and pass them to your transformation, like this:
"*" #> { ns:NodeSeq =>
ns.map{ n =>
val ta = SHtml.textarea("", (t) => //do something)
ta % n.attributes
}
}
That will use SHtml.textarea to create an input, and then add all of the attributes from the existing element to the new one.

Related

#helper.repeat in PlayFramework

I don't understand how to describe #helper.repeat in play2.2.1.
#helper.form(action = routes.Application.send) {
#helper.repeat(
field = form1("input"), min = 3
) {fld =>
#helper.inputText(
field = fld, '_label ->"input"
)
}
}
It is the part fld => #helper.inputText(field = fld) that I can't understand.
What does it mean?
I know Java, so I assume it is a functional writing, but in above code, where does the variable fld come from?
And why the tip of the arrow indicates #helper.inputText(field = fld)?
why is fld the value of field in #helper.inputText?
I have googled, but I couldn't find an enough explanation for a beginner.
I am not sure of Scala's grammar.
Please explain above code for a beginner?
Original Answer
This seems to be a bit overcomplicated. There is no need to assign values by hand. Usually you would write a repeater like this:
#helper.repeat(form1("input"), min = 3) { fld =>
#helper.inputText(fld, '_label ->"input")
}
In functional programming this is a so called higher-order function. You may know other scala built in higher-order functions like map, filter or foreach. #helper.repeat is very similar to foreach. form1("input") refers to a collection of values you want to display. min = 1 tells the repeater to show at least one field. Finally within { fld => ... } you iterate over all values defined in your collection.
In other words: { fld => ... } is just an anonymous function that takes a single Field parameter (fld) and displays a text input for that specific field.
Follow Up
Ok, I'm trying to follow up your questions from the comments. Let's start by the signature of helper.repeat. There is no magic involved here, it's just a regular Scala function:
helper.repeat(field: Field, min: Int)(fieldRenderer: (fld: Field) => Html): Seq[Html]
In Scala, functions can have multiple parameter lists. Here we have two. The first parameter list takes two parameters: field and min. The second parameter list takes a single function as parameter: fieldRenderer. fieldRenderer itself takes again a single parameter (fld) and returns Html.
The important thing is, you are not passing "data" but a function instead. To clear this up:
The signature fieldRenderer: (fld: Field) => Html
is equal to def fieldRenderer(fld: Field): Html
This means, you can do anything within this function, as long as it returns Html. That's exactly what happens in the example at the very top. You pass a Field and return Html. You do that by calling #helper.inputText.
Now repeat first gets a List[Field] from field you pass as first parameter. This list corresponds to the String List of your container class. Also repeat ensures there is at least one element in that list. This is, because you passed 1 as min. Then the function fieldRenderer is applied to all elements in our list.
A pseudo code implementation of repeat could look like this:
def repeat(field: Field, min: Int)(fieldRenderer: (fld: Field) => Html): Seq[Html] {
val list: List[Field] = field.getFields
var output: List[Html] = List()
for (i = 0; i < list.length; i++) {
val element: Field = list.get(i)
val html: Html = fieldRenderer(element)
output += html
}
return output
}

Lift 2.6: How to append a NodeSeq rather than using SetHtml()

I use SetHtml() all the time to swap chunks of HTML as part of the return trip of an ajax call, ie. when my "Edit Item" ajax call returns, I swap the contents of with form elements representing the "Item" to be edited.
Now I'm trying to add a new item to a list (so that "Item 2" appears as the next li under "Item 1"). My html looks like (this is a greatly simplified version) this:
<div data-lift="form.ajax">
<div data-lift="JsCmdTest">
<test:submitbutton></test:submitbutton>
<ul id="targetdiv">
<li>Item 1</li>
</ul>
</div>
</div>
and my Lift code looks like this
class JsCmdTest extends StatefulSnippet
{
def dispatch = { case "render" => render }
def bindStuff(ns: NodeSeq): NodeSeq =
{
bind("test", ns,
"submitbutton" -> SHtml.ajaxButton("Go", ()=> {
val newLi = Jx(<div>Item 2</div>)
(ElemById("targetdiv") ~> JsFunc("appendChild", newLi.toJs)).cmd
})
)
}
def render: (NodeSeq)=>NodeSeq = (ns: NodeSeq) => {
bindStuff(ns)
}
}
When I run this, the ajax call is fired, and the response looks fine, but I get the following error in the browser console:
The server call succeeded, but the returned Javascript contains an error: NotFoundError: Failed to execute 'appendChild' on 'Node': The new child element is null.
WANTED: a way to append replacement HTML to an existing < ul >. I feel like the way I'm going about appending seems way more esoteric than probably needed, but i haven't found any other way to do it.
Thanks in advance!
you're using a very old syntax for binding. If I am not mistaken, a new way for bindings was introduced somewhere in lift-2, and it is the recommended one since somewhere in lift-2.2. (I'll write the syntax below.)
JsFunc is an anonymous function, like def local(a: A): B. You don't need to send anonymous function, you can send the code directly. (See below.)
So, I recommend something like this:
import net.liftweb.http.js.{JsExp, JsCmd, JsCmds}
import net.liftweb.http.js.jquery.JqJE.{JqAppend, JqId}
def render = {
val appendJs: JsExp = JqId("targetdiv") ~> JqAppend(newLi)
"#mySubmitButton" #> SHtml.ajaxButton("Go", () => appendJs.cmd)
}
You'll also have to adapt the HTML a little, like using a normal <button> with id="mySubmitButton".

Cannot access the parameter of a Menu.param from a Lift Snippet

I'm trying to extract the parameter from a Lift Menu.param within a snippet so that I can use it to create a named Comet. However, I get a NullPointerException when I try to pass the parameter to the snippet using SnippetDisptach in my Boot.scala, as suggested here:
http://comments.gmane.org/gmane.comp.web.lift/44299
I've created the Menu item as follows:
object AnItemPage {
// create a parameterized page
def menu = Menu.param[Item]("Item", "Item",
s => fetchItem(s), item => item._id.toString) / "item"
private def fetchItem(s:String) : Box[Item] = synchronized {
ItemDAO.findById(ObjectId.massageToObjectId(s))
}
}
I've added the menu to SiteMap. I've also created a Snippet which I would like to pick up the Item parameter. (I'm using fmpwizard's InsertNamedComet library here):
class AddCometItemPage(boxedItem: Box[Item]) extends InsertNamedComet with DispatchSnippet{
val item : Item = boxedItem.openOr(null)
override lazy val name= "comet_item_" + item._id.toString
override lazy val cometClass= "UserItemCometActor"
def dispatch = null
}
My next step is to crate an instance of this class as demonstrated by David Pollak here:
http://comments.gmane.org/gmane.comp.web.lift/44299
This is what I have added to my Boot.scala:
LiftRules.snippetDispatch.append {
case "item_page" => new AddCometItemPage(AnItemPage.menu.currentValue)
}
My item.html references this snippet:
<div class="lift:item_page">
I get the following null pointer exception when I compile and run this:
Exception occurred while processing /item/5114eb4044ae953cf863b786
Message: java.lang.NullPointerException
net.liftweb.sitemap.Loc$class.siteMap(Loc.scala:147)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.siteMap(Menu.scala:170)
net.liftweb.sitemap.Loc$class.allParams(Loc.scala:123)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.allParams(Menu.scala:170)
net.liftweb.sitemap.Loc$class.net$liftweb$sitemap$Loc$$staticValue(Loc.scala:87)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.net$liftweb$sitemap$Loc$$staticValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.paramValue(Loc.scala:85)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.paramValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.currentValue(Loc.scala:114)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.currentValue(Menu.scala:170)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:107)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:106)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.common.Full.map(Box.scala:553)
net.liftweb.util.NamedPF$.applyBox(NamedPartialFunction.scala:97)
net.liftweb.http.LiftRules.snippet(LiftRules.scala:711)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.http.LiftSession.net$liftweb$http$LiftSession$$findSnippetInstance(LiftSession.scala:1505)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1670)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1669)
Has anybody any idea where I'm going wrong? I've not been able to find a lot of information on Menu.param.
Thank you very much for your help.
f
I have never tried what you are doing, so I am not sure the best way to accomplish it. The way you are using the Loc Param, you are extracting a variable from a URL pattern. In your case, http://server/item/ITEMID where ITEMID is the string representation of an Item, and which is the value that gets passed to the fetchItem function. The function call will not have a value if you just arbitrarily call it, and from what I can see you are requesting a value that is not initialized.
I would think there are two possible solutions. The first would be to use S.location instead of AnItemPage.menu.currentValue. It will return a Box[Loc[Any]] representing the Loc that is currently being accessed (with the parameters set). You can use that Loc to retrive currentValue and set your parameter.
The other option would be to instantiate the actor in your snippet. Something like this:
item.html
<div data-lift="AnItemPage">
<div id="mycomet"></div>
</div>
And then in your AnItemPage snippet, something like this:
class AnItemPage(item: Item) {
def render = "#mycomet" #> new AddCometItemPage(item).render
}
I haven't tested either of those, so they'll probably need some tweaking. Hopefully it will give you a general idea.

How to define a `separator` tag in play-1.x without modifing play's source code

I want to define a tag separator tag, which inside a list tag, can add separator between items.
The sample code is:
List<String> users = new ArrayList<String>();
users.add("Jeff");
users.add("Mike");
#{list users, as: 'user'}
#{separator ' + ' /}
<span>${user}</span>
#{/list}
If I don't use the separator tag, the code will be:
#{list users, as: 'user'}
${user_isFirst ? '' : ' + '}
<span>${user}</span>
#{/list}
The generated html code will be:
<span>Jeff</span> + <span>Mike</span>
I tried defined a fastTag:
public static void _separator(Map<?, ?> args, Closure body, PrintWriter out, GroovyTemplate.ExecutableTemplate template, int fromLine) {
Object value = args.get("arg");
// TODO how to get the value of `as` defined in parent `list` tag?
out.print(value);
}
But the problem is I can't get the value of as defined in list tag (which is user) in this case.
You can create a custom list tag in groovy like this
#{list items:_arg, as:'tmp'}
%{
attrs = [:]
attrs.put(_as, tmp)
}%
#{ifnot tmp_isFirst}${_sep}#{/ifnot}
#{doBody vars:attrs /}
#{/list}
and use it like this
#{myList users, as:'user', sep:','}
${user}
#{/myList}
You should trace into your FastTag implementation. I think you'll see all the variables in scope inside the args map. This is from memory - so, sorry if not.
That said, I think it might be simpler if you copy the Java code for #{list} and add a new parameter, like
#{list users, as: 'user', separator: '+' }
and handle the logic in there. It seems a bit cleaner too from a design point of view - if it is a separator, how come you can put it anywhere you like in the code (and why not put it in twice!).
A final option is to look at Groovy or Java collection operators. http://groovy.codehaus.org/groovy-jdk/java/util/Collection.html

Lift change/remove DOM attribute

Hey i have a little issue and don't find any solution. When my lift app starts first time a select box is disabled by the "disable" attribute. After the user has clicked on button, i want to enable the select box. I tried the following
line
"#car_select [disabled]" #> (None:Option[String])
to remove the "disabled" attribute, but this actually doesn't work. Is there another methode to manipulate or remove DOM attributes?
I don't think there is a CSS Selector rule to remove an attribute, you can try something like this though:
"#car_select" #> { xml: NodeSeq =>
xml match {
case e: Elem =>
Elem(e.prefix, e.label, e.attributes.remove("disabled"), e.scope, e.child)
case other => other
}
}