I'm new to thymeleaf and I'm trying to create a web component. What I want is something as this:
<components:panel><div>hi!</div></components:panel>
translated to something like this:
<div class="panel"><div class="contents"><div>hi!</div></div></div>
I've been trying to extend an AbstractElementTagProcessor, but I can't seem to figure out how to get the tags inside the processed tag:
public class PanelTagProcessor extends AbstractElementTagProcessor {
private static final String ELEMENT_NAME = "panel";
private static final int PRECEDENCE = 10000;
public PanelTagProcessor(final String dialectPrefix) {
super(TemplateMode.HTML, "components", ELEMENT_NAME, true, null, false, PRECEDENCE);
}
#Override
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler) {
// How could I surround contents inside the tag with custom nodes?
}
}
I'd be grateful if someone could help me :)
Regards
Luis
First, I don't think it's possible to do with just a AbstractElementTagProcessor. According to the documentation, those kind of processors "execute on open/standalone tag events only (no processors can be applied to close tags), and have no (direct) access to the element body."
I managed to get it working which an AbstractElementModelProcessor ("execute on complete elements, including their bodies, in the form of IModel objects"), so here is what worked for me.
public class PanelTagProcessor extends AbstractElementModelProcessor {
private static final String TAG_NAME = "panel";
private static final int PRECEDENCE = 10000;
public PanelTagProcessor(String dialectPrefix) {
super(TemplateMode.HTML, dialectPrefix, TAG_NAME, true, null, false, PRECEDENCE);
}
#Override
protected void doProcess(ITemplateContext itc, IModel imodel, IElementModelStructureHandler iemsh) {
IModelFactory modelFactory = itc.getModelFactory();
imodel.replace(0, modelFactory.createOpenElementTag("div", "class", "panel"));
imodel.insert(1, modelFactory.createOpenElementTag("div", "class", "contents"));
imodel.insert(imodel.size() - 2, modelFactory.createCloseElementTag("div"));
imodel.replace(imodel.size() - 1, modelFactory.createCloseElementTag("div"));
}
}
Related
How can I override the test name that appears on the TestNG report? I want to override the name that appears in the middle column (currently shows as the method name). Is this even possible?
I tried to do it like this, but it didn't work.
public class EchApiTest1 extends TestBase {
...
#BeforeTest
public void setUp() {
restClient = new RestClientPost();
this.setTestName( "ech: XXXXXX" );
}
And, the base class:
import org.testng.ITest;
public class TestBase implements ITest {
String testName = "";
#Override
public String getTestName() {
return this.testName;
}
public void setTestName( String name ) {
this.testName = name;
}
}
NOTE: The above code does work when I am viewing the report detail in the Jenkins TestNG plugin report, which shows the overridden test name as a string called "Instance Name:" at the beginning of the Reporter log output. Why, in this case, WHY does a "setTestName()" method alter a string labeled "Instance Name" in the report?
One answer I found had a suggestion like this but I don't know how to pass an ITestResult arg to a AfterMethod method:
#AfterMethod
public void setResultTestName( ITestResult result ) {
try {
BaseTestMethod bm = (BaseTestMethod)result.getMethod();
Field f = bm.getClass().getSuperclass().getDeclaredField("m_methodName");
f.setAccessible(true);
f.set( bm, bm.getMethodName() + "." + your_customized_name );
} catch ( Exception ex ) {
Reporter.log( "ex" + ex.getMessage() );
}
Thoughts?
Please find following code for set custom name of testcase in TestNG reports.
Following features are available in this code.
Dynamic execution on same test-case in multiple time
Set custom test-case name for reports
Set parallel execution of multiple test-cases execution
import java.lang.reflect.Field;
import org.testng.ITest;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import org.testng.internal.BaseTestMethod;
import com.test.data.ServiceProcessData;
public class ServiceTest implements ITest {
protected ServiceProcessData serviceProcessData;
protected String testCaseName = "";
#Test
public void executeServiceTest() {
System.out.println(this.serviceProcessData.toString());
}
#Factory(dataProvider = "processDataList")
public RiskServiceTest(ServiceProcessData serviceProcessData) {
this.serviceProcessData = serviceProcessData;
}
#DataProvider(name = "processDataList", parallel = true)
public static Object[] getProcessDataList() {
Object[] serviceProcessDataList = new Object[0];
//Set data in serviceProcessDataList
return serviceProcessDataList;
}
#Override
public String getTestName() {
this.testCaseName = "User custom testcase name";
// this.testCaseName = this.serviceProcessData.getTestCaseCustomName();
return this.testCaseName;
}
#AfterMethod(alwaysRun = true)
public void setResultTestName(ITestResult result) {
try {
BaseTestMethod baseTestMethod = (BaseTestMethod) result.getMethod();
Field f = baseTestMethod.getClass().getSuperclass().getDeclaredField("m_methodName");
f.setAccessible(true);
f.set(baseTestMethod, this.testCaseName);
} catch (Exception e) {
ErrorMessageHelper.getInstance().setErrorMessage(e);
Reporter.log("Exception : " + e.getMessage());
}
}}
Thanks
I found a "workaround" but I am hoping for a better answer. I want to be able to show this "test name" OR "instance name" value on the HTML report (not just within the Reporter.log output) and I am starting to think its not possible :
#Test(dataProvider = "restdata2")
public void testGetNameFromResponse( TestArguments testArgs ) {
this.setTestName( "ech: " + testArgs.getTestName() );
Reporter.log( getTestName() ); // this magic shows test name on report
....
With this workaround, the user can now identify which test it was by looking at the Reporter.log output but I still wish the name was more prominant.
I suspect the answer lies in writing a TestListenerAdapter that somehow overrides the ITestResult.getTestNameMethod() method? That is the holy grail I am looking for.
The ‘result’ object will automatically pass in the method setResultTestName( ITestResult result )
Make sure you put alwaysRun=true like the following when you have groups defined in your test class otherwise “AfterMethod” will not be excuted.
#AfterMethod (alwaysRun=true)
I am new to play, whenever I use list.add(Object) to the list and print the size of the list, it remains 0 !!!
My Method is to like a tutorial, it checks if the logged-in user has liked this tutorial before, if yes, it increments the likeCount of the tutorial by one, and add the tutorial to the like list of the user. If no, it renders that he already likes it.
since the tutorial is not saved in the list, I am not able to check if it is already liked or not !!!
Models:
#Entity
public class RegisteredUser extends Model {
public String name;
#ManyToMany
public List<Tutorial> contributedTutorials;
public RegisteredUser(String name) {
this.name = name;
this.likeList = newArrayList<Tutorial>();
this.save();
}
}
#Entity
public class Tutorial extends Model {
public String Title;
public int likeCount;
public Tutorial(String title) {
this.title = title;
this.likeCount = 0;
}
Controller:
public Tutorials extends Controller {
public static void likeTutorial() {
if (session.get("RegisteredUserId") != null && session.get("tutID") != null ) {
{
long uId = Long.parseLong(session.get("RegisteredUserId"));
RegisteredUser user = RegisteredUser.findById(uId);
long tId = Long.parseLong(session.get("tutID"));
Tutorial tut = Tutorial.findById(tId);
int x = tut.likeCount;
x++;
if (!(user.likeList.contains(tut)))
// to check that this user didn't like this tut before
{
Tutorial.em().createQuery("update Tutorial set likeCount ="+ x +" where id=" +tId).executeUpdate();
tut.refresh();
user.updateLikeList(tut); // THIS IS NOT WORKING!!!
renderText("You have successfully liked this Tutorial " + user.likeList.size());
}
}
renderText("Opps Something went Wrong!!");
}
}
}
The view :
Like
+You don't need to call the this.save() and this.likeList = newArrayList<Tutorial>(); in the constructor. Actually the latter is syntactically wrong.
+Passing the tutorial ID as a session variable is very wrong. You need to pass it as a GET parameter to the action.
+Replace your check with:
// to check that this user didn't like this tut before
if (! user.likeList.contains(tut)) {
// Tutorial.em().createQuery("update Tutorial set likeCount ="+ x +" where id=" +tId).executeUpdate();
// tut.refresh();
// user.updateLikeList(tut); // THIS IS NOT WORKING!!!
tut.likeCount++;
tut.save();
// Since it's a ManyToMany relationship, you only need to add it to one list and the other will reflect properly if mappedBy properly
user.likeList.add(tut); // note that the example you showed uses contributedTutorials not likeList
user.save();
renderText("You have successfully liked this Tutorial " + user.likeList.size());
}
I made all adjustments You mentioned above
And
The mapping in RegisteredUser Model
#ManyToMany
public List<Tutorial> likeList;
and I have added the mapping to Tutorial Model
#ManyToMany
public List<RegisteredUser> likersList;
and adjusted the method in the controller as follows
if (! user.likeList.contains(tut)) {
tut.likeCount++;
//tut.likersList.add(user); // however this raised an error too! at the next line
tut.save();
//as for the likeCount update, it has worked perfectly, Thank You
// Since it's a ManyToMany relationship, you only need to add it to one list and the other will reflect properly if mappedBy properly
//I have modified Model Tutorial as shown above and it didn't work too !
user.likeList.add(tut);
user.save(); // this raised an error!!
renderText("You have successfully liked this Tutorial " + user.likeList.size());
}
For my case, it is a little bit trickier. I just have another list in person object, it is called state. like below:
public class People {
private int id;
private String name;
private List<State> states;
// Plus setters/getters
}
public class State {
private int id;
private String stateAbbr;
private String stateName;
public State (String stateAbbr, String stateName) {
this.stateAbbr = stateAbbr;
this.stateName = stateName;
}
// Plus setters/getters
}
Action class:
public class PersonAction extends ActionSupport {
private List<People> peopleList;
public List<People> getPeopleList() {
return peopleList;
}
public void setPeopleList(List<People> peopleList) {
this.peopleList = peopleList;
}
//Initial Load method
#Override
public String execute() {
peopleList = new ArrayList<People>();
int alpha = 65;
for(int i = 0; i < 3 ; i++) {
People people = new People();
people.setId(i);
people.setName(String.valueOf((char)alpha++));
peopleList.add(people);
}
for (People people : peopleList){
State state = new State("BC", "BritishColumbia");
List<State> states = new ArrayList<State>();
states.add(state);
state = new State("AC", "AppleColumbia");
states.add(state);
people.setStates(states);
}
return SUCCESS;
}
//Function that handles the form submit
public String updatePerson() {
for(People people : peopleList) {
System.out.println(people.getId() + ":" + people.getName());
}
return SUCCESS;
}
}
JSP page
<s:form action="doUpdate">
<s:iterator value="peopleList" status="stat" var="people">
<s:textfield value="%{#people.name}"
name="peopleList[%{#stat.index}].name" />
<s:iterator value="states" status="stateStatus" var="personState">
<s:textfield value="%{#personState.stateAbbr}"
name="peopleList[%{#stat.index}].states[%{#stateStatus.index}].stateAbbr" label="Abbr State" />
<br />
</s:iterator>
</s:iterator>
<s:submit value="Submit" />
</s:form>
When I submit this page, I got states is [null] in person, why?
Answer for the new question:
If your State class doesn't have a default constructor, S2/OGNL will not be able to instantiate an empty version of the class.
Provide a default (no-argument) constructor, and the person's states list will be populated.
(Too bad both answers can't be accepted ;)
The state property is a property of a specific person. In this case, you need to "connect" the state to persons[%{#stat.index}], so:
<s:textfield ...
name="persons[%{#stat.index}].states[%{#stateStatus.index}]" .../>
The iterator status variable has an index property that avoids the math you're doing on the IteratorStatus (docs) instance:
<s:textfield ... name="persons[%{#stat.index}].name" />
Also, depending on which version of S2 you're using, you may not need the # character.
Well we're trying to render a shitload of lists. Some of them have only one entry and we want them to appear as read only input fields (so users don't get fooled and it's easier to read).
But it seems kinda hard to access the inner selectitems size from outside.
I had my go with overwriting the htmlselectonemenu tag... Is there a different nicer way? Even possible to access it on tag level?
/**
* In case there is only one or less elements in the select list -> set readonly(true)
*/
public class HtmlSelectOneMenuModf extends HtmlSelectOneMenu {
#Override
public boolean isReadonly() {
for (Iterator iterator = getChildren().iterator(); iterator.hasNext();) {
Object obj = iterator.next();
if ( obj instanceof UISelectItems) {
UISelectItemsi = (UISelectItems) obj;
if(i.getSelectItems().size() <=1)
super.setReadonly(true);
}
}
return super.isReadonly();
}
}
We're chilling on JSF 1.2 btw...
Use JSTL fn:length().
<%#taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
...
<h:selectOneMenu value="#{bean.item}" readonly="#{fn:length(bean.items) le 1}">
<f:selectItems value="#{bean.items}" />
</h:selectOneMenu>
I have written an ASP.NET web service.
It looks like this:
WebServices.logic pLogic = new WebServices.logic();
WebServices.manager[] pManager = new PowerManager[1];
pManager[0] = new PowerManager();
pManager[0].CustomerId = "sjsjshd";
pManager[0].state = pLogic.getState("sasj");
return pManager[0];
The pManager class looks like this:
public string _CustomerId;
public int PowerStatus;
public List<ArrayList> _Power;
public string CustomerId
{
get
{
return _CustomerId;
}
set
{
_CustomerId = value;
}
}
public List<ArrayList> Power
{
get
{
return _Power;
}
set
{
_Power = value;
}
}
When I run it, I get a repetition of the results, like so:
<p>
<_CustomerId>sjsjshd</_CustomerId>
<pStatus>0</PowerStatus>
−
<_p>
−
<ArrayOfAnyType>
<anyType xsi:type="xsd:int">1</anyType>
</ArrayOfAnyType>
<ArrayOfAnyType/>
</_p>
<CustomerId>sjsjshd</CustomerId>
−
<p>
−
<ArrayOfAnyType>
<anyType xsi:type="xsd:int">1</anyType>
</ArrayOfAnyType>
<ArrayOfAnyType/>
</p>
</pManager>
However, there is no duplicate values stored (Eg. I store client name in a collection, but only once - count of 1). There are no duplicates stored when I call getState(). This method returns a collection and it contains one value, but the results in XML has a repetition of this.
How comes the results appear to repeat themselves? When running the system, I only get one error.
Thanks
OK, looks like your XML serialization is giving you all the public members of your PowerManager class. Based on the naming convention of starting with an underscore, those members should be private, like this:
private string _CustomerId;
private List<ArrayList> _Power;
You also state "When running the system, I only get one error." What error are you getting?