I try set a dynamic css class value for a pagelink in a simple custom component and can't find any way.
My component ...
<!-- my component template 'testLink' -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<!-- maybe I can set here something dynamic like that ...
<t:pagelink page="mytest" t:id="myLink" class="${myDynCss}">
... but in this case I need to pass the parameter what link is handled
-->
<t:pagelink page="mytest" t:id="myLink">
I want dynamic css class
</t:pagelink>
</html>
The component java code ...
public class TestLink {
#Parameter(required=true)
private int activeId;
#Component
PageLink myLink;
public int getActiveId() {
return activeId;
}
public void setupRender()
{
// I try to set some class attribute here but I find no matching function in myLink
// myLink.setCssStyle();
}
public String getMyDynCss(int currentLinkId) {
if (currentLinkId==activeId)
return "active";
else
return "xxx";
}
}
The page that includes the component ...
<html t:type="layout" title="Test" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<p:app_navigation>
<t:testLink activeId="1000"/>
</p:app_navigation>
</html>
Maybe a silly newbie question but I have still problems to think in Tapestry way.
Every help or useful hint is welcome.
It is not quite clear from your code what the difference between currentLinkId en activeId is and where currentId comes from. I'm almost assuming you have some sort of Loop setup you are not sharing here. But given you can obtain these variables from the enclosing component, you are pretty much there in your commented out code, you just need to remove the argument from your getMyDynCss() method. Like so:
Java:
public class TestLink {
#Property
#Parameter(required=true)
private int activeId;
#Property
#Parameter(required=true)
private int currentId;
public String getMyDynCss() {
if (currentId == activeId) {
return "active";
}
else {
return "xxx";
}
}
}
Your tml:
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<t:pagelink page="mytest" t:id="myLink" class="${myDynCss}">
</html>
Your enclosing component:
<html t:type="layout" title="Test" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<p:app_navigation>
<t:testLink activeId="1000" currentId="somePropertyFromSomewhere"/>
</p:app_navigation>
</html>
My solution use the the life cycle events. If there is any link that has a id that represents the active id (by convention) mark it as active.
My final component template ...
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<!-- convention: id == 'm' + numeric value for active entry -->
<t:pagelink page="mytest" id="m1000">
I'm active
</t:pagelink>
<t:pagelink page="mytest2" id="m1001">
I'm not active
</t:pagelink>
</html>
The java code of the component ...
public class TestLink {
#Parameter(required=true)
private int activeId;
// ... looking for a link with the active id ...
void afterRender(final MarkupWriter writer) {
// works only if the id follows the right convention :-D
String activeElemId="m"+activeId; // <--
Element activeLink=writer.getDocument().getElementById(activeElemId);
if (activeLink!=null)
activeLink.addClassName("active");
}
}
The code that includes the component ...
<html t:type="layout" title="Test" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<p:app_navigation>
<t:testLink activeId="1000"/>
</p:app_navigation>
</html>
Related
I am learning Blazor. I have no experience with component-based programming.
I have two components: a DateRangePicker and a RadzenCheckBox.
<RadzenFieldset Text="Test Component">
<DateRangePicker #ref="calendar" />
<div>
<Radzen.Blazor.RadzenCheckBox TValue="bool" Change="#((args) => txtBoxChange(args))" />
<RadzenLabel Text="Check" />
</div>
</RadzenFieldset>
Now, the requirement is simple. If the checkbox is clicked, show two calendars and show one calendar if it's unchecked.
I wrote the following code:
#code{
DateRangePicker calendar;
public void txtBoxChange(bool args)
{
if (args == true) //shows one calendar when checked
calendar.ShowOnlyOneCalendar = true;
else //shows two calendars when unchecked
calendar.ShowOnlyOneCalendar = false;
}
}
This works fine.
But I get a warning:
Component parameter 'ShowOnlyOneCalendar' should not be set outside of its component.
I have read some blogs about this warning, which suggest making parent and child component relationship for communication between components. But these are not parent and child.
What am I doing wrong?
What is the best way to achieve such a requirement and not have this warning?
What am I doing wrong?
Instead of using an imperative programming (component.Parameter1=v1) way, a Component Parameter is supposed be passed in declarative syntax :
<Component Parameter1="#v1" Parameter2="#v2" />
Note you're assigning values to [Parameter] directly:
calendar.ShowOnlyOneCalendar = true;
That's why Blaozr complains. In other words, you need change it in following way:
<DateRangePicker ShowOnlyOneCalendar="#showOnlyOne" />
How to fix
Always follow this pattern like other SPA:
(render)
Data -----------> View
For example, your code could be rewritten as below:
<DateRangePicker ShowOnlyOneCalendar="#flag" />
...
#code{
private bool flag = false;
public void txtBoxChange(bool args)=> flag = args;
}
(Here we have a flag data, and we should render the view according to the data)
Or if you do want to use an imperative programming way, you need to invoke a method and avoid assigning values to the [Parameter] properties directly.
<DateRangePicker #ref="calendar" />
...
#code{
DateRangePicker calendar;
public void txtBoxChange(bool args)
{
calendar.A_Wrapper_Method_That_Changes_The_Parameter(args);
// as suggested by #Uwe Keim,
// `InvokeAsync(StateHasChanged)` is better than `StateHasChanged()`
InvokeAsync(StateHasChanged);
}
}
I want to enable or disable a textarea depending on a condition that evalueates from the model, and I am using the textarea tag helper.
In other words, something like this:
<textarea asp-for="Doc" #(Model.MustDisable ? "disabled" : "")></textarea>
But I got the following design-time error: The tag helper 'textarea' must not have C# in element's attribute declaration area.
Then I tried:
<textarea asp-for="Doc" disabled='#(Model.MustDisable ? "disabled" : "")'></textarea>
which did not show any design time error but it renders like this:
Model.MustDisable==true renders disabled='disabled' AND Model.MustDisable==false renders disabled.
So the text area will always be disabled.
Then I tried (removing the 's):
textarea asp-for="Doc" disabled=#(Model.MustDisable ? "disabled" : "")></textarea>
which did not show any design time error but it renders the same as the previous one.
How can I implement this the right way?
It is actually very simple, the disable attribute is already working as you want - you can pass in a boolean value:
<textarea asp-for="Doc" disabled="#Model.MustDisable"></textarea>
if false the disabled attribute is not rendered:
<textarea></textarea>
if true the disabled attribute is set to "disabled":
<textarea disabled="disabled"></textarea>
I was facing the same issue with select tag helper, i tried few things and it worked.
Try this-
<textarea asp-for="Doc" disabled="#(Model.MustDisable ? "disabled" : null)"></textarea>
The textarea tag helper does not have direct support to conditionally render a disabled text area. But you can always extend the TextAreaTagHelper and add this feature.
So create a new class which inherits from the TextAreaTagHelper class.
[HtmlTargetElement("textarea", Attributes = ForAttributeName)]
public class MyCustomTextArea : TextAreaTagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("asp-is-disabled")]
public bool IsDisabled { set; get; }
public MyCustomTextArea(IHtmlGenerator generator) : base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (IsDisabled)
{
output.Attributes["disabled"] = "disabled";
}
base.Process(context, output);
}
}
In your _ViewImports.cshtml file, using the #addTagHelper directive, specify the assembly where the above class is defined so that our new tag helper is available in other razor views.
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
Now in your views, you can use it like
#model YourSomeViewModel
<textarea asp-for="Doc" asp-is-disabled="Model.MustDisable"></textarea>
where SomeViewModel has a Doc and MustDisable property.
public class YourSomeViewModel
{
public string Doc { set;get; }
public bool MustDisable { set;get; }
}
I am posting this separately since I don't have enough reputation to add a comment to Shyju's answer.
If you inherit from one of the default tag helpers and then register both the default tag helpers and your custom tag helper in _ViewImports.cshtml, then both tag helpers will be executed for the specified tags.
For the following:
[HtmlTargetElement("textarea", Attributes = ForAttributeName)]
public class MyCustomTextArea : TextAreaTagHelper
{
private const string ForAttributeName = "asp-for";
...
With the following _ViewImports.cshtml:
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
Both MyCustomTextArea and TextAreaTagHelper will be executed for each textarea tag.
I did not notice any problems with the output generated for textareas, but I have run into problems inheriting from other default tag helpers. The solution is to remove the default tag helper in _ViewImports.cshtml.
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
#removeTagHelper "Microsoft.AspNet.Mvc.TagHelpers.TextAreaTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
Here is my attempted implementation understanding of PageObjects/Pagefactory as applied to Webdriver:
1. Create the following structure in eclipse
--> com.example.qa.pageobjects
--> LoginPage.java
Every class in this package has something like:
#FindBy(how = How.NAME, using = "logonName")
private WebElement logonNameField;
and the Methods, call Webelement, and call methods on them, like:
logonNameField.sendKeys("username");
Which are called from ScenrioTests.
--> HomePage.java (i go there after i login)
--> Page.java (abstract)
--> com.example.qa.setup
--> Browser.java
--> FirefoxBrowser.java (Code specific to FFox)
--> ChromeBrowser.java (Code Specific to Chrome)
--> com.example.qa.test
--> Scenario1234.java
--> Scenario2345.java
These Scenario Classes instantiate the PageObjects, and Call methods in them, while the Browser setup is only called Once per test run.
Now the question is:
Should i declare a method like below and call Pagefactory ?
public MyPage method() {
Call the Methods like Login() etc
return PageFactory.initElements(driver, MDNSLoginPage.class);
}
Or, Should i Call the same PageFactory from default Constructor
Is my understanding / implementation correct ?
I think I kind of follow your no 1 approach . I will try to answer by illustrating my implementation but I am not sure whether it would be useful for your purposes.
I have a baseTest class that I load with common methods that can be used across my test classes . (eg. instantiate browser, open login page etc)
abstract class TestBase {
//somewhere
protected static LoginPage goToLoginPage(){
driver.get(loginPage);
return PageFactory.initElements(driver, LoginPage.class);
}
protected static void startBrowser(Browser browser){}
//implemntation
}
Then, in my test classes that inherit the abstract class I use it in the following manner
#BeforeClass
public static void setup(){
//use a common method to start browser
startBrowser(Browser.FIREFOX);
}
#Test
public void canLogInToHomePage(){
//start my test like this
LoginPage loginPage =goToLoginPage();
}
Ignoring the bad style, is it possible to include a page reference to a custom ASP.Net control that is a nested class?
My initial guess was...
Backing Class:
namespace Root.Namespace {
public class OuterClass {
public class InnerClass : Control {
//...
}
}
}
Page:
<!-- ... -->
<%# Register tagprefix="Prefix" namespace="Root.Namespace" %>
<!-- ... -->
<Prefix:OuterClass.InnerClass />
<!-- ... -->
However, that was throwing errors. Based on http://msdn.microsoft.com/en-us/library/c76dd5k1.aspx and the fact that a dot (.) in a tag name is not allowed, I am guessing that there is no way to reference an inner class via that method, but I am not sure.
Update: This turned into a blog post, with updated links and code, over at my blog: https://egilhansen.com/2008/12/01/how-to-take-control-of-style-sheets-in-asp-net-themes-with-the-styleplaceholder-and-style-control/
The problem is pretty simple. When using ASP.NET Themes you do not have much say in how your style sheets are rendered to the page.
The render engine adds all the style sheets you have in your themes folder in alphabetic order, using the <link href=”...” notation.
We all know the order of the style sheets are important, luckily asp.nets shortcomings can be circumvented by prefixing the style sheets with 01, 02, ... , 99, and thus forcing the order you want (see Rusty Swayne blog post on the technique for more information).
This is especially important if you use a reset style sheet, which I highly recommend; it makes it much easier to style a site in a consistent form across browsers (take a look at Reset Reloaded from Eric Meyer).
You also miss out of the possibility to specify a media type (e.g. screen, print, projection, braille, speech). And if you prefer to include style sheets using the #import method, you are also left out in the cold.
Another missing option is Conditional Comment, which is especially useful if you use an “ie-fix.css” style sheet.
Before I explain how the StylePlaceholder and Style control resolve the above issues, credit where credit is due, my solution is inspired by Per Zimmerman’s blog post on the subject.
The StylePlaceHolder control is placed in the header section of your master page or page. It can host one or more Style controls, and will remove styles added by the render engine by default, and add its own (it will only remove styles added from the current active theme).
The Style control can both host inline styles in-between it’s opening and closing tags and a reference to a external style sheet file through its CssUrl property. With other properties you control how the style sheet it renders to the page.
Let me show an example. Consider a simple web site project with a master page and a theme with three style sheets – 01reset.css, 02style.css, 99iefix.cs. Note: I have named them using prefixing technique described earlier, as it makes for a better design time experience. Also, the tag prefix of the custom controls is “ass:”.
In the master page’s header section, add:
<ass:StylePlaceHolder ID="StylePlaceHolder1" runat="server" SkinID="ThemeStyles" />
In your theme directory, add a skin file (e.g. Styles.skin) and add the following content:
<ass:StylePlaceHolder1runat="server" SkinId="ThemeStyles">
<ass:Style CssUrl="~/App_Themes/Default/01reset.css" />
<ass:Style CssUrl="~/App_Themes/Default/02style.css" />
<ass:Style CssUrl="~/App_Themes/Default/99iefix.css" ConditionCommentExpression="[if IE]" />
</ass:StylePlaceHolder1>
That is basically it. There are a more properties on the Style control that can be used to control the rendering, but this is the basic setup. With that in place, you can easily add another theme and replace all the styles, since you only need to include a different skin file.
Now to the code that makes it all happen. I must admit that the design time experience have some quirks. It is probably due to the fact that I am not very proficient in writing custom controls (in fact, these two are my first attempts), so I would very much like input on the following. In a current WCAB/WCSF based project I am developing, I am seeing errors like this in Visual Studios design view, and I have no idea why. The site compiles and everything works online.
Example of design time error in Visual Studio http://www.egil.dk/wp-content/styleplaceholder-error.jpg
The following is the code for the StylePlaceHolder control:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
[assembly: TagPrefix("Assimilated.Extensions.Web.Controls", "ass")]
namespace Assimilated.WebControls.Stylesheet
{
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[DefaultProperty("SkinID")]
[ToolboxData("<{0}:StylePlaceHolder runat=\"server\" SkinID=\"ThemeStyles\"></{0}:StylePlaceHolder>")]
[ParseChildren(true, "Styles")]
[Themeable(true)]
[PersistChildren(false)]
public class StylePlaceHolder : Control
{
private List<Style> _styles;
[Browsable(true)]
[Category("Behavior")]
[DefaultValue("ThemeStyles")]
public override string SkinID { get; set; }
[Browsable(false)]
public List<Style> Styles
{
get
{
if (_styles == null)
_styles = new List<Style>();
return _styles;
}
}
protected override void CreateChildControls()
{
if (_styles == null)
return;
// add child controls
Styles.ForEach(Controls.Add);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// get notified when page has finished its load stage
Page.LoadComplete += Page_LoadComplete;
}
void Page_LoadComplete(object sender, EventArgs e)
{
// only remove if the page is actually using themes
if (!string.IsNullOrEmpty(Page.StyleSheetTheme) || !string.IsNullOrEmpty(Page.Theme))
{
// Make sure only to remove style sheets from the added by
// the runtime form the current theme.
var themePath = string.Format("~/App_Themes/{0}",
!string.IsNullOrEmpty(Page.StyleSheetTheme)
? Page.StyleSheetTheme
: Page.Theme);
// find all existing stylesheets in header
var removeCandidate = Page.Header.Controls.OfType<HtmlLink>()
.Where(link => link.Href.StartsWith(themePath)).ToList();
// remove the automatically added style sheets
removeCandidate.ForEach(Page.Header.Controls.Remove);
}
}
protected override void AddParsedSubObject(object obj)
{
// only add Style controls
if (obj is Style)
base.AddParsedSubObject(obj);
}
}
}
And the code for the Style control:
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
[assembly: TagPrefix("Assimilated.Extensions.Web.Controls", "ass")]
namespace Assimilated.WebControls.Stylesheet
{
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[DefaultProperty("CssUrl")]
[ParseChildren(true, "InlineStyle")]
[PersistChildren(false)]
[ToolboxData("<{0}:Style runat=\"server\"></{0}:Style>")]
[Themeable(true)]
public class Style : Control
{
public Style()
{
// set default value... for some reason the DefaultValue attribute do
// not set this as I would have expected.
TargetMedia = "All";
}
#region Properties
[Browsable(true)]
[Category("Style sheet")]
[DefaultValue("")]
[Description("The url to the style sheet.")]
[UrlProperty("*.css")]
public string CssUrl
{
get; set;
}
[Browsable(true)]
[Category("Style sheet")]
[DefaultValue("All")]
[Description("The target media(s) of the style sheet. See http://www.w3.org/TR/REC-CSS2/media.html for more information.")]
public string TargetMedia
{
get; set;
}
[Browsable(true)]
[Category("Style sheet")]
[DefaultValue(EmbedType.Link)]
[Description("Specify how to embed the style sheet on the page.")]
public EmbedType Type
{
get; set;
}
[Browsable(false)]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public string InlineStyle
{
get; set;
}
[Browsable(true)]
[Category("Conditional comment")]
[DefaultValue("")]
[Description("Specifies a conditional comment expression to wrap the style sheet in. See http://msdn.microsoft.com/en-us/library/ms537512.aspx")]
public string ConditionalCommentExpression
{
get; set;
}
[Browsable(true)]
[Category("Conditional comment")]
[DefaultValue(CommentType.DownlevelHidden)]
[Description("Whether to reveal the conditional comment expression to downlevel browsers. Default is to hide. See http://msdn.microsoft.com/en-us/library/ms537512.aspx")]
public CommentType ConditionalCommentType
{
get; set;
}
[Browsable(true)]
[Category("Behavior")]
public override string SkinID { get; set; }
#endregion
protected override void Render(HtmlTextWriter writer)
{
// add empty line to make output pretty
writer.WriteLine();
// prints out begin condition comment tag
if (!string.IsNullOrEmpty(ConditionalCommentExpression))
writer.WriteLine(ConditionalCommentType == CommentType.DownlevelRevealed ? "<!{0}>" : "<!--{0}>",
ConditionalCommentExpression);
if (!string.IsNullOrEmpty(CssUrl))
{
// add shared attribute
writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css");
// render either import or link tag
if (Type == EmbedType.Link)
{
// <link href=\"{0}\" type=\"text/css\" rel=\"stylesheet\" media=\"{1}\" />
writer.AddAttribute(HtmlTextWriterAttribute.Href, ResolveUrl(CssUrl));
writer.AddAttribute(HtmlTextWriterAttribute.Rel, "stylesheet");
writer.AddAttribute("media", TargetMedia);
writer.RenderBeginTag(HtmlTextWriterTag.Link);
writer.RenderEndTag();
}
else
{
// <style type="text/css">#import "modern.css" screen;</style>
writer.RenderBeginTag(HtmlTextWriterTag.Style);
writer.Write("#import \"{0}\" {1};", ResolveUrl(CssUrl), TargetMedia);
writer.RenderEndTag();
}
}
if(!string.IsNullOrEmpty(InlineStyle))
{
// <style type="text/css">... inline style ... </style>
writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css");
writer.RenderBeginTag(HtmlTextWriterTag.Style);
writer.Write(InlineStyle);
writer.RenderEndTag();
}
// prints out end condition comment tag
if (!string.IsNullOrEmpty(ConditionalCommentExpression))
{
// add empty line to make output pretty
writer.WriteLine();
writer.WriteLine(ConditionalCommentType == CommentType.DownlevelRevealed ? "<![endif]>" : "<![endif]-->");
}
}
}
public enum EmbedType
{
Link = 0,
Import = 1,
}
public enum CommentType
{
DownlevelHidden = 0,
DownlevelRevealed = 1
}
}
So what do you guys think? Is this a good solution to the asp.net theme problem? And what about the code? I would really like some input on it, especially in regards to the design time experience.
I uploaded a zipped version of the Visual Studio solution that contains the project, in case anyone is interested.
Best regards, Egil.
Found the answer to my own question.
The reason for the rendering errors I am getting in design mode, is an apparent bug in Visual Studio SP1, which Microsoft has yet to fix.
So the above code works as expected, also in design mode, as long as you just include the custom controls in a pre compiled assembly, and not through another project in the same solution.
See the link above for a more detailed explanation of how and why.
Works very smoothly.
For those like me that never remember syntax of <% tags here's what you need to add to the top of the master page definition and the skin file to register the namespace.
<%# Register TagPrefix="ass" Namespace="Assimilated.WebControls.Stylesheet" %>
I'm not sure I want that much 'ass' all over my code, but otherwise I like it.
Oh and if this is really your first custom control great job. I know it was inspired by someone else's code but it at least appears to have all the right attributes and interfaces.
Re: using specific media CSS file, you can use the #media CSS statement, works fine.