Yet another dumb newbie question Tables and Comet

39 views
Skip to first unread message

Donald McLean

unread,
Jul 7, 2010, 12:30:15 PM7/7/10
to liftweb
First, let me just say that this group has been great helping me
fumble around through the prototype/tech evaluation. I've decided that
Scala/LiftWeb is my favorite technology - now I just have to convince
the powers that be.

Before I can do that, I have to finish the prototype and to do that, I
have to get a dynamically updating table working. It doesn't matter
whether individual cells or the whole table gets updated - it isn't
going to be large enough to make a visible difference to the user.

The table contains a fixed number of rows - one row for each service
that is being monitored. Each row contains the host name, name of
service, current status and {(if status is up a button to stop it) or
(if status is down a button to start it)}

The html currently looks like this:

<div>
<table>
<lift:comet type="Servers">
<tr>
<td>
<f:host_name>not available</f:host_name>
</td>
<td>
<f:service_name>not available</f:service_name>
</td>
<td>
<f:service_status>not available</f:service_status>
</td>
<td>button goes here</td>
</tr>
</lift:comet>

</table>
</div>

The current version of Servers.scala is shown below. Obviously, it
doesn't work, error:

XML Parsing Error: prefix not bound to a namespace
Location: http://protom2.stsci.edu:20035/liftdemo/
Line Number 106, Column 21: <f:host_name>not
available</f:host_name>

Binding looks like it should be fairly simple, except that binding
with Comet and binding with tables appear to be slightly different and
combining them is just confusing the heck out of me.

So if you have some comments or advice, I would be exceptionally grateful.

Donald

------------------ Servers.scala ------------------------------
package code.snippet

import xml.NodeSeq

import edu.stsci.meb.ModuleLoader
import edu.stsci.dads.service.{ServiceEntry, ServiceKey, EBServiceManager}
import collection.mutable.{ArrayBuffer, HashSet}
import net.liftweb.http.{RenderOut, CometActor}
import net.liftweb.common.Full

class Servers extends CometActor {
println("[Servers.init] enter.")

override def defaultPrefix = Full("f")

val serviceManager: EBServiceManager =
ModuleLoader.getRoleObject("services").asInstanceOf[EBServiceManager]

def render: RenderOut = {
val keys = serviceManager.getServices
val entries: ArrayBuffer[ServiceEntry] = new ArrayBuffer[ServiceEntry]
for (key <- keys) {
if (key.getServiceName.equals("YPServer")) {}
else {
val next = serviceManager.getEntry(key)
entries += next
}
}

val a: NodeSeq = entries.flatMap(entry => bind(
"host_name" --> entry.getKey.getHostName,
"service_name" --> entry.getKey.getServiceName,
"service_status" --> entry.getStatus.toString))

return new RenderOut(a)
}
}

--
Family photographs are a critical legacy for
ourselves and our descendants. Protect that
legacy with a digital backup and recovery plan.

Join the photo preservation advocacy Facebook group:
http://www.facebook.com/home.php?ref=logo#/group.php?gid=148274709288

David Pollak

unread,
Jul 7, 2010, 12:48:36 PM7/7/10
to lif...@googlegroups.com
In order for the Comet component to be found, it must be in the comet package.  I've take a little liberty in re-writing your code:

package code.comet


import xml.NodeSeq

import edu.stsci.meb.ModuleLoader
import edu.stsci.dads.service.{ServiceEntry, ServiceKey, EBServiceManager}
import collection.mutable.{ArrayBuffer, HashSet}
import net.liftweb.http.{RenderOut, CometActor}
import net.liftweb.common.Full

class Servers extends CometActor {
 println("[Servers.init] enter.")

 override def defaultPrefix = Full("f")

 val serviceManager: EBServiceManager = ModuleLoader.getRoleObject("services").asInstanceOf[EBServiceManager]

 def render =
   for {
     key <- serviceManager.getServices if key.getServiceName == "YPServer"
     entry = serviceManager.getEntry(key)
     node <- bind("host_name" -> entry.getKey.getHostName,
                  "service_name" -> entry.getKey.getServiceName,
                  "service_status" -> entry.getStatus.toString)
   } yield node
}

Hope this helps.



--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Blog: http://goodstuff.im
Surf the harmonics

Derek Chen-Becker

unread,
Jul 7, 2010, 1:02:30 PM7/7/10
to lif...@googlegroups.com
I'm not sure if you got the example from the book, but I updated the Comet chapter to emphasize the package usage.

Derek

Donald McLean

unread,
Jul 7, 2010, 1:16:41 PM7/7/10
to lif...@googlegroups.com
The package naming was my error. I forgot to change it when I changed
it from a snippet.

This example looks good (though I don't quite follow all of the
syntax) except that it doesn't compile:

/Users/dmclean/IdeaProjects/dsb/dads/onegui_webapp/liftDemo/src/scala/code/comet/Servers.scala:20:
error: type mismatch;
found : Array[scala.xml.Node]
required: net.liftweb.http.RenderOut


key <- serviceManager.getServices if key.getServiceName == "YPServer"

^
one error found

Lift 2.0 w/Scala 2.7.7

Thank you,

Donald

David Pollak

unread,
Jul 7, 2010, 1:30:29 PM7/7/10
to lif...@googlegroups.com
On Wed, Jul 7, 2010 at 10:16 AM, Donald McLean <dmcl...@gmail.com> wrote:
The package naming was my error. I forgot to change it when I changed
it from a snippet.

This example looks good (though I don't quite follow all of the
syntax) except that it doesn't compile:

/Users/dmclean/IdeaProjects/dsb/dads/onegui_webapp/liftDemo/src/scala/code/comet/Servers.scala:20:
error: type mismatch;
 found   : Array[scala.xml.Node]
 required: net.liftweb.http.RenderOut
    key <- serviceManager.getServices if key.getServiceName == "YPServer"
    ^
one error found

Lift 2.0 w/Scala 2.7.7

Okay... here's an updated version that should compile (sorry I can't test it without your libraries) and also has more comments:


package code.comet

import xml.NodeSeq

import edu.stsci.meb.ModuleLoader
import edu.stsci.dads.service.{ServiceEntry, ServiceKey, EBServiceManager}
import collection.mutable.{ArrayBuffer, HashSet}
import net.liftweb.http.{RenderOut, CometActor}
import net.liftweb.common.Full

class Servers extends CometActor {
 println("[Servers.init] enter.")

 override def defaultPrefix = Full("f")

 val serviceManager: EBServiceManager = ModuleLoader.getRoleObject("services").asInstanceOf[EBServiceManager]

 def render =
   (for {
     // get the keys, but only worry about the ones that have a service name of "YPServer"

     key <- serviceManager.getServices if key.getServiceName == "YPServer"
     // get the entry for the key
     entry = serviceManager.getEntry(key)

     // bind the entry to the template

     node <- bind("host_name" -> entry.getKey.getHostName,
                  "service_name" -> entry.getKey.getServiceName,
                  "service_status" -> entry.getStatus.toString)

   // yield the results
   } yield node): NodeSeq
}

The problem was coming from Render with has a return type of RenderOut.  A RenderOut can be composed of NodeSeq, JavaScript or a combination of both.  There are implicit conversions from NodeSeq, JsCmd, etc. into a RenderOut.  There's also an implicit conversion from Seq[Node] (Array[Node] is a subclass of Seq[Node]) to NodeSeq... but in Scala implicits don't chain.  So, we have to explicitly say that the for comprehension results in a NodeSeq (this will tip the Scala compiler that the Array[Node] needs the implicit conversion to the NodeSeq, which then will be converted into a RenderOut).

Does this help?

Thanks,

David
 
--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Donald McLean

unread,
Jul 7, 2010, 1:45:12 PM7/7/10
to lif...@googlegroups.com
On Wed, Jul 7, 2010 at 1:30 PM, David Pollak
<feeder.of...@gmail.com> wrote:
>
> Does this help?

That is not just helpful, it is perfect.

I'm going to try and figure out the buttons and the updates (learn by
doing, ya know) but don't be surprised if I have questions...

Thank you again,

Donald

David Pollak

unread,
Jul 7, 2010, 2:07:53 PM7/7/10
to lif...@googlegroups.com

Looking forward to your questions!
 

Thank you again,

Donald


--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Donald McLean

unread,
Jul 8, 2010, 11:27:49 AM7/8/10
to lif...@googlegroups.com
On Wed, Jul 7, 2010 at 2:07 PM, David Pollak
<feeder.of...@gmail.com> wrote:
>
> Looking forward to your questions!

Ok then, I have three questions (see code for current version of
Servers.scala, below).

1. There seems to be a long delay between my pressing the button and
the call to ajaxStartStopHandler (several seconds). This doesn't seem
normal so what did I do wrong?

2. When ajaxStartStopHandler finally gets called, it gets called
twice. Again, what did I do wrong?

3. The definition of userContext is:

object userContext extends SessionVar[UserContext](null)

It is properly set (at least according to another part of the code)
but in CommandRunner it is always null. How can that be?

Thank you,

Donald

----------------------- Servers.scala
package code.comet

import xml.NodeSeq

import edu.stsci.meb.ModuleLoader
import collection.mutable.{ArrayBuffer, HashSet}
import net.liftweb.common.Full
import net.liftweb.http.{SHtml, RenderOut, CometActor}
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JE.{JsRaw, Str}
import net.liftweb.util.Helpers.TimeSpan
import edu.stsci.dads.service.{ServiceStatus, ServiceEntry,
ServiceKey, EBServiceManager}
import net.liftweb.util.ActorPing
import net.liftweb.http.js.JsCmds.{Replace, Alert}
import edu.stsci.dads.agents.{CommandAgent, StopServerAgent, StartServerAgent}
import java.util.{HashMap, Date}
import code.snippet.userContext

class Servers extends CometActor {
println("[Servers.init] enter.")

override def defaultPrefix = Full("f")

val serviceManager: EBServiceManager =
ModuleLoader.getRoleObject("services").asInstanceOf[EBServiceManager]

val tickLength = new TimeSpan(2500L)

def ajaxStartStopHandler(str: String): JsCmd = {
println("[Servers.ajaxStartStopHandler] Received " + str)

val data = str.split(",")
new CommandRunner(data).start

Alert("Sending " + data(0))
}

def renderOutput(): NodeSeq =
<lift:embed what={"/template-servers"}/>

def render =
(for{
// get the keys, but only if they service name is NOT "YPServer"

key <- serviceManager.getServices if key.getServiceName != "YPServer"


// get the entry for the key
entry = serviceManager.getEntry(key)

// bind the entry to the template

node <- bind("host_name" -> entry.getKey.getHostName,
"service_name" -> entry.getKey.getServiceName,

"service_status" -> entry.getStatus.toString,
"button" -> button(entry))

// yield the results
} yield node): NodeSeq

def button(entry: ServiceEntry) = {
entry.getStatus match {
case ServiceStatus.up => <button
onclick={SHtml.ajaxCall(Str("stop," + entry.getKey.getHostName + "," +
entry.getKey.getServiceName), ajaxStartStopHandler
_)._2}>stop</button>
case ServiceStatus.down => <button
onclick={SHtml.ajaxCall(Str("start," + entry.getKey.getHostName + ","
+ entry.getKey.getServiceName), ajaxStartStopHandler
_)._2}>start</button>
case _ => <button enabled="false">waiting...</button>
}
}

// schedule a ping every 2.5 seconds so we redraw
ActorPing.schedule(this, Tick, tickLength)
override def lowPriority: PartialFunction[Any, Unit] = {
case Tick => {
println("[Servers.lowPriority] Got tick " + new Date());

Replace("serverContent", renderOutput)

// schedule an update in 2.5 seconds
ActorPing.schedule(this, Tick, tickLength)
}
}
}

class CommandRunner(data: Array[String]) extends Thread {
val commandMap = Map[String, CommandAgent]("start" -> new
StartServerAgent, "stop" -> new StopServerAgent)

override def run = {
println("[CommandRunner.run] enter.")
val agent = commandMap.get(data(0)).get

val executeData: HashMap[String, Object] = new HashMap[String, Object]
val context = userContext.get
println("[CommandRunner.run] context is: " + context)
executeData.put("context", context)
executeData.put("host", data(1))
executeData.put("server", data(2))
val result = agent.execute(executeData)

println("[CommandRunner.run] exit, result: " + result)
}
}

Derek Chen-Becker

unread,
Jul 8, 2010, 11:53:06 AM7/8/10
to lif...@googlegroups.com
Well, one thing I'd like to point out is that I think that ajaxCall is overkill for what you're doing (or at least what you're showing here). It doesn't appear that you need to be computing anything on the client side, which is the purpose of ajaxCall. Instead, I would rewrite your code to use ajaxButton, replacing the button method:


def button(entry: ServiceEntry) = {
   entry.getStatus match {
     case ServiceStatus.up => SHtml.ajaxButton("stop", () => ajaxStartStopHandler("stop," + entry.getKey.getHostName + "," + entry.getKey.getServiceName))
     case ServiceStatus.down =>  SHtml.ajaxButton("stop", () => ajaxStartStopHandler("start," + entry.getKey.getHostName + "," + entry.getKey.getServiceName))

     case _ => <button enabled="false">waiting...</button>
   }
 }

Not sure why you're getting the delay on the call, though. It shouldn't matter that you're using ajaxCall in that case, and the client-side function that you're using there is simply a String literal...

Derek


--

David Pollak

unread,
Jul 8, 2010, 2:37:06 PM7/8/10
to lif...@googlegroups.com
On Thu, Jul 8, 2010 at 8:27 AM, Donald McLean <dmcl...@gmail.com> wrote:
On Wed, Jul 7, 2010 at 2:07 PM, David Pollak
> Looking forward to your questions!

Ok then, I have three questions (see code for current version of
Servers.scala, below).

1. There seems to be a long delay between my pressing the button and
the call to ajaxStartStopHandler (several seconds). This doesn't seem
normal so what did I do wrong?

Why do you think there's a long delay?  Also, please see Derek's suggestion about
 

2. When ajaxStartStopHandler finally gets called, it gets called
twice. Again, what did I do wrong?


There is a little spinning icon in the browser when Lift does an Ajax call.  Is that icon spinning?  The only thing I can think of is that the browser thinks it's failed to send the Ajax request to the server and sends it again.
 
3. The definition of userContext is:

object userContext extends SessionVar[UserContext](null)

It is properly set (at least according to another part of the code)
but in CommandRunner it is always null. How can that be?

Because SessionVars (and any ***Var) is thread/scope dependent.  Your userContext is outside the request scope (it's on a different thread).  The easiest way to address the issue is send the value of userContext to CommandRunner.

But, there's a much easier way to do what you're trying to do:

def ajaxHandler(agent: CommandAgent, host: String, server: String): JsCmd = {
  println("Got JS call")
  val context = userContext.get // we're in the Ajax scope... capture the reference to the context... this is closed in the function passed to schedule

  ActorPing.schedule(() => agent.execute(HashMap("context" -> context, "host" -> host, "server" -> server)), 0)

  Alert("Sending "+agent)

}

def button(entry: ServiceEntry) = {
   entry.getStatus match {
     case ServiceStatus.up => SHtml.ajaxButton("start", () => ajaxHandler (new StartServerAgent, entry.getKey.getHostName, entry.getKey.getServiceName))
     case ServiceStatus.down =>  SHtml.ajaxButton("stop", () => ajaxHandler (new StopServerAgent, entry.getKey.getHostName, entry.getKey.getServiceName))

     case _ => <button enabled="false">waiting...</button>
   }
 }


Also:


 override def lowPriority: PartialFunction[Any, Unit] = {
   case Tick => {
     println("[Servers.lowPriority] Got tick " + new Date());

     partialUpdate(Replace("serverContent", renderOutput)) // make sure we send the update

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Donald McLean

unread,
Jul 13, 2010, 1:49:45 PM7/13/10
to lif...@googlegroups.com
Ok, next question.

I have two different Comet regions on the page that are supposed to be
updated by two different Comet actors. Doesn't seem to work though. Is
that because it can't be done that way or because I'm just doing
something stupid?

Thank you,

Donald

David Pollak

unread,
Jul 13, 2010, 2:18:29 PM7/13/10
to lif...@googlegroups.com

You can have as many CometActors on a page as you want.  How are you updating the CometActors?  Are you using reRender(false) or partialUpdate()?  In the code example you sent around last week, you were pulling a template on each rendering event.  That's generally not necessary.

Also, there is a limitation to the CometActors in that they have to be on the page when it is initially loaded.  You cannot dynamically add/remove CometActors via Ajax.
 

Thank you,

Donald


--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Donald McLean

unread,
Jul 13, 2010, 5:00:57 PM7/13/10
to lif...@googlegroups.com
Part of my problem was something stupid on my part.

Next: Is there a JsCmd that I can use that does nothing? I tried both:

Replace("serverContent", renderOutput)

Alert("Sending " + agent)

They appear to be the source of much slowness (the replace call took >
26 seconds)

Donald

David Pollak

unread,
Jul 13, 2010, 5:18:46 PM7/13/10
to lif...@googlegroups.com
On Tue, Jul 13, 2010 at 2:00 PM, Donald McLean <dmcl...@gmail.com> wrote:
Part of my problem was something stupid on my part.

Next: Is there a JsCmd that I can use that does nothing? I tried both:

Noop
 

Replace("serverContent", renderOutput)

Alert("Sending " + agent)

They appear to be the source of much slowness (the replace call took >
26 seconds)

Donald

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Donald McLean

unread,
Jul 14, 2010, 9:05:49 AM7/14/10
to lif...@googlegroups.com
On Tue, Jul 13, 2010 at 5:18 PM, David Pollak
<feeder.of...@gmail.com> wrote:
>
>
> On Tue, Jul 13, 2010 at 2:00 PM, Donald McLean <dmcl...@gmail.com> wrote:
>>
>> Part of my problem was something stupid on my part.
>>
>> Next: Is there a JsCmd that I can use that does nothing? I tried both:
>
> Noop

Is there some reason why it's not listed in the scaladoc?

TylerWeir

unread,
Jul 14, 2010, 9:24:20 AM7/14/10
to Lift
I think it may be due to Noop being a val. Maybe ScalaDoc omits vals.

val Noop: JsCmd = _Noop

and _Noop is a case object, which appears to be omitted as well.
JsIf is also a case object and is omitted.

Hrm.

Conjecture on my part.


On Jul 14, 9:05 am, Donald McLean <dmclea...@gmail.com> wrote:
> On Tue, Jul 13, 2010 at 5:18 PM, David Pollak
>
> <feeder.of.the.be...@gmail.com> wrote:

Donald McLean

unread,
Jul 14, 2010, 9:40:02 AM7/14/10
to lif...@googlegroups.com
Let me rephrase that then.

It should be listed in the ScalaDoc, otherwise it's useless. I was
looking in the scaladoc for JsCmd looking at the different choices and
IT ISN'T THERE which means that unless someone happens to already know
about it or is reading the source code, They Are Never Going To Find
It!

This is exactly the kind of thing that drives newbies crazy.

TylerWeir

unread,
Jul 14, 2010, 9:49:47 AM7/14/10
to Lift
Sorry that it's not there, we're working on improving the docs.



On Jul 14, 9:40 am, Donald McLean <dmclea...@gmail.com> wrote:
> Let me rephrase that then.
>
> It should be listed in the ScalaDoc, otherwise it's useless. I was
> looking in the scaladoc for JsCmd looking at the different choices and
> IT ISN'T THERE which means that unless someone happens to already know
> about it or is reading the source code, They Are Never Going To Find
> It!
>
> This is exactly the kind of thing that drives newbies crazy.
>

Donald McLean

unread,
Jul 14, 2010, 10:01:14 AM7/14/10
to lif...@googlegroups.com
I apologize for getting a little short. The folks here have been amazing.

Donald

Ross Mellgren

unread,
Jul 14, 2010, 10:36:28 AM7/14/10
to lif...@googlegroups.com
PastedGraphic-3.png

Donald McLean

unread,
Jul 14, 2010, 10:59:31 AM7/14/10
to lif...@googlegroups.com
Ok, you're right - I missed that.

Unfortunately, that isn't very useful. In fact, I would categorize it
as useless for newbies since they probably would not think to look
there.

There isn't anywhere in the scaladoc where one can go to see what ALL
of the available (JsCmd)s are, regardless of how they are implemented.
That is what is really needed.

Naftoli Gugenheim

unread,
Jul 14, 2010, 3:22:42 PM7/14/10
to liftweb
Is it on the assembla wiki?


Donald McLean

unread,
Jul 14, 2010, 3:36:25 PM7/14/10
to lif...@googlegroups.com
Why would I look on the Wiki? (unless there is a link or mention of
relevant content in the ScalaDoc)

The point of ScalaDoc should be to explain the important information
about a class and that most definitely includes many things that the
Scala community does not presently include, as a rule. Terse code is
often good, terse documentation is often useless.

Donald

Derek Chen-Becker

unread,
Jul 14, 2010, 6:27:36 PM7/14/10
to lif...@googlegroups.com
There are a couple of things that factor into this. One is that we're still missing a lot of documentation in the code. We're trying to remedy that, but it's a big undertaking. The other factor is the vscaladoc (and ScalaDoc2, if you're going to use that) are still evolving and don't always have the same level of support for features as does JavaDoc. One example is that @see doesn't work, AFAIK, which is a huge help to cross-referencing things like this.

Derek

Derek Chen-Becker

unread,
Jul 14, 2010, 6:29:52 PM7/14/10
to lif...@googlegroups.com
Also, let me just plug the Javascript chapter in Exploring Lift. It has an overview of all of the JsCmd and JsExp subclasses with brief descriptions. It might be slightly out-of-date (I need to double-check), but it should include most of the common items. Now, I'll be the first to say that the ScalaDoc should be first-rate and isn't, but we're working on making all of the docs better.

Derek

Reply all
Reply to author
Forward
0 new messages