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! :-)
Related
app.component.html
<div class="inlineBlock">
<select [(ngModel)]="portId" id="portDropdownMenu" (change)="externalFilterChanged()">
<option *ngFor="#portId of portIds">{{portId}}</option>
</select>
</div>
<div class="container">
<ag-grid-ng2 #agGrid
[gridOptions]="gridOptions"
[columnDefs]="myColumnDefs"
[rowData]="myRowData"
enableColResize
rowSelection="multiple"
enableSorting
enableFilter
[isExternalFilterPresent]="isExternalFilterPresent"
[doesExternalFilterPass]="doesExternalFilterPass"
rowHeight="30"
headerHeight="40"
enableRangeSelection
suppressContextMenu
suppressMenuColumnPanel
rowGroupPanelShow="always"
rememberGroupStateWhenNextData
groupDefaultExpanded="-1"
groupHideGroupColumns
groupUseEntireRow
(modelUpdated)="onModelUpdated()"
(filterChanged)="onFilterChanged()">
</ag-grid-ng2>
</div>
app.component.ts
public isExternalFilterPresent() {
return this.portType != "All Ports";
}
public doesExternalFilterPass(node) {
switch (this.portType) {
case "1": return node.data.Port === "1";
case "2": return node.data.Port === "2";
case "3": return node.data.Port === "3";
default: return true;
}
}
public externalFilterChanged() {
var newValue = (<HTMLInputElement>document.getElementById("portDropdownMenu")).value
this.portType = newValue;
this.gridOptions.api.onFilterChanged();
}
public onFilterChanged() {
if (this.gridOptions.api.isAnyFilterPresent()) {
this.gridOptions.api.setRowData(this.gridOptions.rowData);
this.gridOptions.api.refreshView();
}
console.log("filter changed ...");
}
By console.log(this.gridOption.isAnyFilterPresented()), I notice the filter does exist when dropdown menu is updated. However, the grid is not updating according to external filter.
I am pretty sure "isExternalFilterPresent()" and "doesExternalFilterPass(node)" run through and provides the correct return value. My understanding is that ag-grid will take care of the rest but it is not doing it. Any idea?
there is a solution to this issue.
declare the two functions: isExternalFilterPresent,doesExternalFilterPass in type script,
get an instance of the gridOptions,
private gridOptions:GridOptions;
and in the constructor:
this.gridOptions = <GridOptions>{};
then
this.gridOptions.isExternalFilterPresent = this.isExternalFilterPresent.bind(this);
this.gridOptions.doesExternalFilterPass = this.doesExternalFilterPass.bind(this);
now, you will be able to access the component's variables inside those functions:
this.myVariable
full description the problem+solution:
https://github.com/ceolter/ag-grid-ng2/issues/121
doesExternalFilterPass and isExternalFilterPresent are an arrow function so this has no meaning inside these functions. Below is how they should be used -
/**
* This property is an arrow function, which binds `this` to the Angular Component.
* It's necessary otherwise `this` is undefined inside the function because
* it's not called as a method of the class by the Datagrid.
* It's called as `doesExternalFilterPass(node)` and not as `component.doesExternalFilterPass(node)`
*/
doesExternalFilterPass = (node: RowNode): boolean => {
return node.data.currency >= this.currencyFilter;
}
Source - https://github.com/ceolter/ag-grid-angular/issues/121
An update on this issue:
The problem for me is the scope in angular 2 variables. this.portType is undefined in isExternalFilterPresent() and doesExternalFilterPass(node) even I initialized properly in the constructor. My fix is to retrieve portType from HTML each time those two functions are called.
It is not a nice fix, hopefully, someone could come up with something better. And if anyone could explain why the portType variable was undefined?
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".
I'm using the form helper and a custom form template to render my forms in the Play framework like this:
#(lang: Lang)(myForm: Form[MyModel])
#import play.i18n._
#import helper._
#implicitField = #{ FieldConstructor(formTemplate.f) }
#form ( action = routes.Application.index() ){
#inputText(
field = myForm("username"),
'_label -> Messages.get(lang, "username")
)
}
When the template is called with different values for lang, the label is displayed in the respective language.
However, when the form is submitted, error messages are always displayed in the main language. (i.e. for Required fields it's always This field is required.)
As the answer to this post mentioned, I changed the default error messages like so in my language files (currently only 2):
messages.en:
username=Username
error.required=This field is required
messages.nl:
username=Gebruikersnaam
error.required=Dit veld is verplicht
How can I make sure the errors are printed in the correct language?
I've already tried doing the following in my custom template, but without success:
#(elements: helper.FieldElements)
<!-- snipped some HTML code -->
<span class="help">
#(elements.infos(elements.args.get('_lang) match {
case Some(language) => language.asInstanceOf[play.api.i18n.Lang]
case None => new Lang("en","uk")
}).mkString(", "))
</span>
And by adding '_lang -> lang to my #inputText call.
I'm used to programming in Java and have only done some Scala in the Play templates. I'm using Play 2.0.4.
I have found the easiest way of doing this (note: I program in Java) is by defining a static method in one of your models that returns the users language:
public class User{
import play.i18n.Lang;
//simplified
public static Lang getLanguage(){
if(session("language" != null){
return Lang.forCode(session.get("language"));
} else {
return Lang.forCode("en"); //default language
}
}
You can then call this static function in your Scala form template like this:
<span class="errors">#elements.errors(User.getLanguage()).mkString(", ")</span>
to display translated errors based on the default error messages in your messages.xx files.
As a general matter, if your error codes are also found in the messages.xx resource files, they get localized, even if you program a custom validator somewhere else. You don't have to have the Lang in scope or call Messages() yourself. E.g. in Scala Play:
val validPhone = """\+?[0-9_\-\. \(\)]*$""".r
val phoneCheckConstraint: Constraint[String] = Constraint("constraints.phonecheck")({
plainText =>
val errors = plainText match {
case validPhone() => Nil
case _ => Seq(ValidationError("error.phonenumber"))
}
if (errors.isEmpty) {
Valid
} else {
Invalid(errors)
}
})
If you merely have
error.phonenumber=Invalid phone number
in your messages.en file and translated versions in other messages.xx files they will get localized by Play even though no Lang was in scope at the point of declaration. So no need to pass Lang around other than in your templates and elsewhere for explicit Messages() calls.
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.
I need to loop through some elements in the page and then, for each one, if it had a class beginning with, for example, "C", do something.
$('#dialog li').each(function(){
if ($(this).hasClass("^C")){
//do something
}
}
It may sound silly, but what selector/method should I use on the if clause?
Carefull with $('#dialog li[class^="C"]')! It will only match elements, whose class attribute starts with "C" not ones with a class starting with C. For example it will not match <li class="foo Clown">.
AFAIK what you want is not possible mit jQuery alone. You would need to loop through the classes and check each separatly. Something like:
$('#dialog li').filter(function(){
var classes = this.className.split(/\s/);
for (var i = 0, len = classes.length; i < len; i++)
if (/^C/.test(classes[i])) return true;
return false;
}).each( ... )
Alternativly you should consider changing your approach, and give all elements an additional class and filter by that. This has the addvantage that it can also be used in CSS:
<li class="Clown Clown-Funny">
<li class="Clown Clown-Sad">
<li class="Clown Clown-Rodeo">
Try the Attribute Starts With Selector. As a bonus, there is no need for the extra if.
$('#dialog li[class^="C"]').each(function() {
// do something
});
I don't think that there is a built-in selector to test for classes starting with a string.
There is a selector to test if an attribute starts with a string, so if you know that your elements only have one class (or always start with the class in question), you could do:
$(this).is("class^='C'")
If, however, you can't guarantee either of the above conditions, you would have to manually split out and test each class defined on the element, as described here.
You can try that selector. That will work if there are two or more classes like this "Clown foo doo" and
You can try however something like this $('#dialog li[class*="C"]') in which case will select anything that include the "C" letter, for excample "exCelence foo".
If you are interested in counting how many classes start with "C" no matter of their position (no matter if they are at the beginning, at the and or somewhere in the middle) then you can try something like this:
$('#dialog li[class^="C"]').each(function() {
var classes = div.attr("class").split(" ");
var count = 0;
$.each(classes, function(i, v){ if(v.indexOf('C')!=-1) count++; });
if (count == 1) alert("It has one 'C' class");
if (count>1) alert("It more than one 'C' class");
}
Try something like $('#dialog li[class^="C"]')
For more elaborate filters than "starts with" you can use the filter() function:
$('#dialog li').filter( function() {
// return true for every element that matches your condition
return this.className.match(/^c/) != null;
}).each(function(){
//do something
}