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

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.

Related

Play Framework request attributes with typed key

I seem to have issues accessing the attributes of the request attributes map in Play. Following the explanation offered by Play (Link), I should get the correct data from the attributes, but the Option is returned as None.
My structure is as follows. One controller (later injected named as "sec") has the typed attribute for shared access to it:
val AuthenticatedAsAttr: TypedKey[AuthenticatedEmail] = TypedKey("AuthenticatedAs")
The type AuthenticatedEmail is defined in the companion object of this controller as a case class:
case class AuthenticatedEmail(email: String)
The filter passes the attribute to the next request:
val attrs = requestHeader.attrs + TypedEntry[AuthenticatedEmail](sec.AuthenticatedAsAttr, AuthenticatedEmail(email))
nextFilter(requestHeader.withAttrs(attrs))
When trying to then access this attribute in another controller, the returned Option is None:
val auth = request.attrs.get(sec.AuthenticatedAsAttr)
I confirmed via println that the value is definitely in request.attrs but run out of options to debug the issue successfully. A fraction of the println output below.
(Request attrs,{HandlerDef -> HandlerDef(sun.misc .... ,POST, ... Cookies -> Container<Cookies(Cookie ... , AuthenticatedAs -> AuthenticatedEmail(a#test.de), ... })
My Scala version is 2.12.6, Play Framework version 2.6.18. Any help is highly appreciated.
It turns out that the TypedKey must be within an object, not an inject-able controller. So moving it to an object like the following resolves the issue:
object Attrs {
val AuthenticatedAsAttr: TypedKey[AuthenticatedEmail] = TypedKey("AuthenticatedAs")
}
The reason is the implementation of TypedKey (Link), which does not contain an equals method and therefore reverts to comparing memory references.

Wicket NumberTextField in Kotlin throws ClassCastException when submitted

I'm having some issues with a Wicket (8.0.0-M4) NumberTextField in Kotlin (1.1.0).
My stripped-down form looks like this:
class Test : AbstractWebPage() {
val housenumberModel: Model<Int> = Model<Int>()
val housenumber = NumberTextField<Int>("housenumberModel", housenumberModel)
val form: Form<Unit> = object : Form<Unit>("adressForm") {}
override fun onInitialize() {
super.onInitialize()
form.add(housenumber.setRequired(false))
form.add(object : SubmitLink("submit") {
override fun onSubmit() {
super.onSubmit()
println(housenumberModel.`object`) // this is line 28
}
})
add(form)
}
}
After submitting the form I get the following stacktrace:
java.lang.ClassCastException: java.lang.String cannot be cast to
java.lang.Number
at com.mycompany.test.pages.Test$onInitialize$1.onSubmit(Test.kt:28)
at org.apache.wicket.markup.html.form.Form.delegateSubmit(Form.java:1312)
at org.apache.wicket.markup.html.form.Form.process(Form.java:979)
at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:802)
at org.apache.wicket.markup.html.form.Form.onRequest(Form.java:715)
at org.apache.wicket.core.request.handler.ListenerRequestHandler.internalInvoke(ListenerRequestHandler.java:301)
at org.apache.wicket.core.request.handler.ListenerRequestHandler.invoke(ListenerRequestHandler.java:250)
at org.apache.wicket.core.request.handler.ListenerRequestHandler.invokeListener(ListenerRequestHandler.java:210)
at org.apache.wicket.core.request.handler.ListenerRequestHandler.respond(ListenerRequestHandler.java:203)
at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:912)
at org.apache.wicket.request.RequestHandlerExecutor.execute(RequestHandlerExecutor.java:65)
at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:283)
at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:253)
at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:221)
at org.apache.wicket.protocol.http.WicketFilter.processRequestCycle(WicketFilter.java:262)
at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:204)
at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:286)
[...]
If I use
val housenumberModel: Model<Int> = Model.of(0)
instead of
val housenumberModel: Model<Int> = Model<Int>()
everything works fine. But since my NumberTextField is optional I don't want to have it pre-initialized with 0.
Me and my colleagues were trying to change the type signature of the Model in every way we could imagine but came to no solution. A co-worker suggested to write a custom Wicket converter since Kotlins Int is represendeted as a primitive type (From the docs: "On the JVM, non-nullable values of this type are represented as values of the primitive type int.") Even though I don't know yet if this would work it seems like an overkill for me.
Another hack I could think of: writing some JavaScript to delete the zero from the input field. Also not really something I would want to do.
Question: Is there a simple solution to my problem?
(And as a bonus-question: has already anyone written a larger Wicket application in Kotlin and could tell me if this combination is ready for prime time to develop a critical project with this stack or is my problem just the tip of the iceberg?)
[edit]
Solution as pointed out by svenmeier:
Using
val housenumber = NumberTextField<Int>("housenumberModel", housenumberModel, Int::class.java)
works.
Or as an alternative:
val housenumbervalue: Int? = null
val housenumberModel: IModel<Int> = PropertyModel<Int>(this, "housenumbervalue")
val housenumber = NumberTextField<Int>("housenumberModel", housenumberModel)
Because of type erasure your NumberTextField cannot detect the generic type parameter of your model. Since your model object is null, it cannot be used to derive the type either.
In this case Wicket assumes a String model object type :/.
Either provide the type to the NumberTextField explicitly, or use a model that keeps its generic information, e.g. a PropertyModel.
There is a way to tell wicket about the type you want, it is by adding the type in the constructor. More here.
In Java it looks like this:
new NumberTextField<Integer>("housenumberModel", housenumberModel, Integer.class);

Creating custom DOM events with scalajs

I can't find a way to create custom events with scala-js. For instance, with js you can create a custom event like the following (taken from here):
var event = new CustomEvent('build', { 'detail': elem.dataset.time });
However, there is no constructor for CustomerEvent or Event in scala-js that accept arguments. Also, subclassing either such as:
class DrawEvent extends Event {
override def `type` = "draw"
}
leads to
Uncaught TypeError: undefined is not a function
when trying to construct via new DrawEvent()
Any ideas?
To instantiate javascript classes in ScalaJs you have to use js.Dynamic.newInstance:
This should work for your use case:
val event = js.Dynamic.newInstance(js.Dynamic.global.CustomEvent)("build", js.Dynamic.literal(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
There is more info available at the remarks portion (all the way at the bottom) of:
http://www.scala-js.org/doc/calling-javascript.html
Here is the same solution using some imports to make it shorter
import js.Dynamic.{ global => g, newInstance => jsnew, literal => lit }
val event = jsnew(g.CustomEvent)("build", lit(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
If you want to stay in the typed DOM (assuming you are talking about the scala-js-dom library), you can do:
new CustomEvent().initCustomEvent('build', false, false, elem.dataset.time)
The constructor you are using is actually only specified in DOM 4 (see MDN).

How to execute tests on the argument that a controller passes to the view in Play Framework

In our play application every controller function fetches data from the database (or some other way) and passes these values to the result
def index = Action { implicit request =>
val newsItems: List[String] = fetchNewsFromDB()
Ok(views.html.home.index(newsItems))
}
def fetchNewsFromDB() = List("Headline1", "Headline2")
I am writing tests using specifiactions (based on the documentation http://www.playframework.com/documentation/2.2.x/ScalaTest)
According to this documentation by controller as follows. In the next test I want to make sure that the index page contains a headline. I do this by checking if there exists a div with the class "headline"
"Example Page#index" should {
"should contain a headline" in {
val controller = new TestController()
val result: Future[SimpleResult] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText.toLowerCase must contain("<div class=\"headline\"")
}
}
However I would rather check whether the list newsItems which the controller passes to the view is nonempty.
What is the best way to do this?
Is it possible to this in a generic way for which little modification of the controllers is required?
I too was frustrated that I couldn't intercept the parameters on their way to the template - and in fact it can become extremely difficult to even get the template to render at all in tests if you have a lot of "state" in your pages (for example, implicits that provide the user object, navigation helpers etc).
What I ended up doing was putting in an extra "seam" for testability in my controllers; in my tests, I extend the controller under test, replacing the HTML rendering function with a mocked one, which I can then use to verify the parameters.
Here's a simple example based on your "news" Action; first, the controller, which is no longer an object so we can extend it:
object Application extends ApplicationController
trait ApplicationController extends Controller {
def newsAction = Action {
Ok(renderNews("this is the news"))
}
def renderNews(s:List[String]):Html = html.sandbox(s)
}
The renderNews method gives us the all-important "test seam". I think it also actually improves the readability of controller methods too, which is nice :-)
Now, the unit test:
class ApplicationSpec extends Specification with Mockito {
val mockedNewsRenderer = mock[List[String] => Html]
val controller = new ApplicationController {
override def renderNews(s:List[String]) = mockedNewsRenderer(s)
}
"Application News Controller" should {
"Pass a non-empty list of news items to the template" in {
val result = controller.newsAction(FakeRequest())
status(result) must beEqualTo(200)
val captor = ArgumentCaptor.forClass(classOf[List[String]])
there was one(mockedNewsRenderer).apply(captor.capture())
val theArgument = captor.getValue
theArgument.isEmpty must beFalse
}
}
}
We create a mock to stand-in for the renderNews function, extend the controller so that we can substitute it in (note that we don't change anything else about it of course), and then call the action as normal. Note that we still get a standard Play Result so we can still check status codes etc, but then, we can use the Mockito verify functionality that's built into Specs2, together with Mockito's ArgumentCaptor facility to assert that our template was indeed called, and that it was supplied with a non-empty list of strings.
This approach has worked well for me - it makes it possible to get really good code coverage of your controllers with fast-running and easy-to-write unit tests.
You have a very good question and a very valid point on testing controllers, but I'm afraid it can't be done easily. The problem is that the views compile to Scala functions meaning when you call views.html.home.index(newsItems) it will return an object of Html, which already has the Html put together and compiled. If you would like to test what get's passed in you need to intercept it before the view is called.
To solve this you would have to rewrite your controllers, by moving all your business logic out of the controller and only have the necessary request handling code there. That would almost be easier to test.

Really confused about snippets

I have a problem with my lift view. The thing is, I am making an expensive remote rest-api call twice - where I should really need to do it only once.
But I can't figure out how to solve this.
Basically I have an HTML template like this, that needs to display the list of users and their count:
//UserSearchResults.html
Num users: <span class="lift:UserSearchResults.userCount"></span>
User list:
<ul>
<lift:UserSearchResults.userList>
<li><user:userName/></li>
</lift:UserSearchResults.userList>
</ul>
And then I have an actual snippet that goes and retrieves the list of users from the rest-api server. However, note that it actually does this TWICE - once to count the number of users, and once to render the list.
//UserSearchResults.scala
/** Get list of users from api */
def users: List[User] = {
val url = "http://server/rest-api/user-search";
val result = io.Source.fromURL(url).mkString
//... parse users into List[User] and return it
return entries
}
/** Render user count */
def userCount =
"* *" #> users.length //<-- ONE call
def userList(in: NodeSeq): NodeSeq = {
users.flatMap(user => Helpers.bind("user", in, //<--SECOND call
"userName" -> user.user_name))
}
Is there a better place to put the api call? Is there like a "constructor" for the snippet, that I can use cache the user list, and to share it across all the functions in the class?
Any help is appreciated.
If UserSearchResults is a class (as opposed to an object), then there will be a per-request instance of that class. As such, all you have to do is change your def users to a lazy val users and you should be good to go.
If your snippet extends StatefulSnippet, you can just save the list in an instance variable. Another option would be to put the list into a RequestVar. Then it could also be accessed from other snippets.