How do I update an HTML class from a CometActor - scala

In response to some asynchronous event on the server, I want to update the class of an HTML node in order to reflect its updated status. I know the id of the node, and the class I want to change it to. What JsCmd do I need to use to update the class? In general, where can I find a good reference on the JsCmd's and what they do?
A simple example:
case class UpdateClass(id: String, htmlClass: String)
class ClassUpdater extends CometActor {
override def lowPriority: scala.PartialFunction[scala.Any, scala.Unit] = {
case UpdateClass(id, htmlClass) =>
partialUpdate(Noop /* now what? */)
}
def render = NodeSeq.Empty
}
So if I had the HTML:
<html><body>
<lift:comet type="ClassUpdater"/>
<div id="foo" class="bar">insert text here</div>
</body></html>
If I sent the message UpdateClass("foo", "baz") to my ClassUpdater, I want the class of my div to change to baz.

Edit: I’ve found a better way to do it. The old code is now more of a blueprint for more complicated stuff.
Looks like there is a more straightforward way of doing it without jQuery:
SetElemById("foo", JE.Str("baz"), "className")
which translates to a JavaScript call
document.getElementById("foo").className = "baz";
Note that JE.Str("baz") can be any JsExp and if you could also do something like
SetElemById("foo", JE.Str("baz"), "firstChild", "className")
which would change the class of the first child. (See: SetElemById)
You can have a look at the code for the JsCMD trait for what else is possible with build-in commands.
In case you want to something more complicated, however, something like this might help you. It sends a jQuery command which will change the class in #oldId to newClass.
case class ChangeClassAtId(oldId: String, newClass: String) extends JsCmd {
def toJsCmd = """try {
$(""" + ("#" + oldId).encJs + """).attr("class", """ + newClass.encJs + """);
} catch (e) {}"""
}
Changing all occurrences of a class everywhere is a bit more complicated:
case class ChangeClass(oldClass: String, newClass: String) extends JsCmd {
def toJsCmd = """try {
$(""" + ("." + oldClass).encJs + """).each(function(){
$(this).addClass(""" + newClass.encJs + """).removeClass(""" + oldClass.encJs + """);
});
} catch (e) {}"""
}
You should use it instead of Noop of course.

EDIT - I misread the question. My answer merely updates the contents of the div.
Check out: http://github.com/lift/lift/blob/master/examples/example/src/main/scala/net/liftweb/example/comet/Clock.scala
You'll want something like this:
case UpdateClass(id, htmlClass) =>
partialUpdate(SetHtml(id, Text("TEXT TO SHOVE INTO DIV")))

Related

Calling a function as a parameter in a template call in Play

In Play/Scala; I am confused as to why this won't compile:
#(tabNames: Seq[String])
#isActive(idx: Int) = {
#if(idx < 1) {
"active"
} else {
""
}
}
<ul class="nav nav-tabs">
#for((tab, idx) <- tabNames.zipWithIndex) {
#views.html.generic.navLi("tab" + idx.toString, tab, isActive(idx))
}
</ul>
The error reads:
found : play.twirl.api.HtmlFormat.Appendable [error] (which
expands to) play.twirl.api.Html [error] required: String [error]
#views.html.generic.navLi("tab" + idx.toString, tab, isActive(idx))
It doesn't recognise the call to isActive within the call to the template and I have tried multiple variations, e.g. #isActive(idx), isActive(#idx) ${isActive(idx)} (as suggested here), etc. This template generates a navigation bar, passing in tab names and checking to see if the nav li should be active (configured by class name/JS).
It just seems that the syntax must be different when calling a function within another template call - I can't get it right.
Documentation makes a distinction between
reusable code block
reusable pure code block
Note the subtle difference in # usage between
#isActive(idx: Int) = {
#if(...
#isActive(idx: Int) = #{
if(...
Reusable pure code block can have arbitrary return type. In your case, to have String as return type, you could write:
#isActive(idx: Int) = #{
if(idx < 1) {
"active"
} else {
""
}
}
The Play documentation is a little light in this area; while it is certainly possible and useful to declare "functions" in a twirl template, the return type seems to be locked to (effectively) Html - i.e. your "block" (as it is referred to in the documentation) must render HTML.
The quickest solution, as suggested in the documentation, is to relocate the logic into a Scala class; for something as simple as this, an object is the easiest way to get this done, i.e.:
package views
object ViewHelper {
def isActive(idx: Int):String = {
if(idx < 1) {
"active"
} else {
""
}
}
}
and:
<ul class="nav nav-tabs">
#for((tab, idx) <- tabNames.zipWithIndex) {
#views.html.generic.navLi("tab" + idx.toString, tab, ViewHelper.isActive(idx))
}
</ul>
The advantages of this approach include testability and reusability. Just be careful to avoid getting carried away with what a ViewHelper can do - database lookups etc are a bad idea here! :-)

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

Scala Lift - Comet actor initialization message

I have the following Comet actor:
class ChatComet extends CometActor with CometListener {
private var messages: List[ChatItem] = Nil
def registerWith = ChatServer
override def lowPriority = {
case v: List[ChatItem] =>
messages = v;
reRender()
}
def render = {
"li *" #> messages.map(message =>
".name *" #> message.name &
".text *" #> message.value
)
}
Which distributes messages around various rooms, what I don't have is a way to limit which messages end up where, so a message from RoomA ends up in RoomA not in RoomB.
I've heard you can initialize comet actors with some kind of message which in my case could serve as the room id.
Is this the right way of doing things? If so how would I go about doing it?
Thanks in advance for any help, much appreciated :)
In the template, name the comet actor, like so:
<div class="l:comet?type=CometActor;name=roomA"> stuff </div>
In your CometActor, then match on the name, like so:
def lowpriority = {
case NewComment(room, msg) => {
name.map(x => x match {
case "roomA" => {
stuff
}
}
}
Now, you probably don't have explicit names like "roomA" but you can keep a list of your room names, and then simply match against them.
Give your Comet Actors names associated with the chat rooms. Then a new Actor will be built per-room. You won't need to do any special handling in your Actor itself; unless you want to have special features depending on the room.
You can see a nice example referenced on the Lift wiki, which shows how you can use a snippet to assign the names to your Comet Actors.
For initializing CometActors in general, check out my answer on S.param within a CometActor. That being said, Dave and Tyler's answers seem more appropriate to what you want to do.

How to write your own matcher in Circumflex?

As asked here, I need to run a Circumflex action only if a condition is met. Currently, I do it like this:
get("/") = requireLogin {
...
}
Now Circumflex supports Matchers, which can be put right within the get() specification:
get("/mail" & Host("localhost"))
How can I write my own matcher, so the above requireLogin gets closer to the Circumflex style? I wish to write something like this:
get("/" & IsLoggedIn) = {
...
}
Here is a simple implementation. All it takes is to define an object with the desired name, extending from AtomicMatcher (as this one implements add() etc) and implement the name and apply methods:
object IsLoggedIn extends AtomicMatcher {
def name = "IsLoggedIn"
def apply = {
// Not shown: figuring out whether user is logged in or not
if(loggedIn) {
Some(List(new MatchResult(name)))
}
else {
sendRedirect("/login")
throw new ResponseSentException;
}
}
}
If the user is logged in (ie. the action can be executed), you need to return a list of at least one MatchResult. If you don't want to execute the action, do something else and either return None or throw a ResponseSentException, which seems to be the preferred way.

Lift passing data with a Link

What is the "best" way to pass Data with a Link?
In the moment i save the Data in a SessionVAr and get it out on the other side. But that feels very clumsy. Is there a other way to do that?
object activeHw extends SessionVar[Box[Hw]](Empty)
object AddHwScreen extends LiftScreen {
object hw extends ScreenVar(activeHw.is.getOrElse(Hw.createRecord))
addFields(() => hw.is)
def finish() = {
redirectTo("/hw")
}
}
class HwSnippet extends StatefulSnippet with PaginatorSnippet[Hw] {
var dispatch: DispatchIt = {
case "showAll" => showAll _
}
def showAll(xhtml: NodeSeq): NodeSeq = {
page.flatMap(hw => {
(".hwDetail *" #> link ("hw/detail", () => activeHw.set(Full(hw)), Text("Detail")) &
".hwSn *" #> hw.sn).apply(xhtml)
})
}
def redirectToHome = {
redirectTo("/hw")
}
}
If you have:
object MyInfo extends RequestVar("nothing")
Then:
val str = (new java.util.Date).toString
S.redirectTo("/", () => MyInfo.set(str))
You capture the value by calculating it before doing the redirectTo, then execute a function that sets a RequestVar to the value. The RequestVar can be accessed by the target page.
If it's in a SessionVar then there's no need to pass it in the link. Just access it in the next Snippet.
Update: Sorry, I misread your question. You are already doing that but would rather pass it on the link (as a query parameter?). Generally that isn't the Lift way of doing things: someone could change the value, etc. If you really want to you can add a query param to the link and then access it in the next Snippet with S.param("the-name").