Spring Boot + Thymeleaf css is not applied to template - css
I am evaluating Thymeleaf and Flying Saucer for pdf generation from templates, and I am having a problem with applying css to my Thymeleaf template. I already read the relevant questions & answers here, here, and here; but none of the suggested solutions fixed my problem.
This is how my resources folder looks like:
So I am using the default directories that Spring will look for. And that's how the head tag looks like in my template.html:
<head>
<title>Spring Boot and Thymeleaf Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="../static/css/style.css" th:href="#{/css/style.css}"/>
</head>
If I inline my css in template.html then the generated pdf file will be styled properly (so there shouldn't be a problem with how I generate the pdf). However, when I try to link to the css file as shown above the generated pdf is not styled (so the css is not applied).
Lastly, I can access my css file at http://localhost:8080/css/style.css, so there doesn't seem to be a problem with Spring serving the static content.
For completeness, this is how I generate the pdf:
private final SpringTemplateEngine templateEngine;
private final Log log;
#Autowired
public PdfGenerator(SpringTemplateEngine templateEngine) {
this.templateEngine = templateEngine;
log = LogFactory.getLog(getClass());
}
public void generate(HttpServletRequest servletRequest, HttpServletResponse servletResponse, ServletContext servletContext) {
// Parse the pdf template with Thymeleaf
Locale locale = getLocale(servletRequest);
WebContext context = new WebContext(servletRequest, servletResponse, servletContext, locale);
context.setVariable("user", buildDummyUser());
context.setVariable("discounts", buildDummyDiscounts());
String html = templateEngine.process("template", context);
// Create the pdf with Flying Saucer
try (OutputStream outputStream = new FileOutputStream("generated.pdf")) {
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(html);
renderer.layout();
renderer.createPDF(outputStream);
} catch (IOException | DocumentException e) {
log.error("Error while generating pdf", e);
}
}
I am using WebContext instead of Context because I was getting the following error with Context:
org.thymeleaf.exceptions.TemplateProcessingException: Link base "/css/style.css" cannot be context relative (/...) unless the context used for executing the engine implements the org.thymeleaf.context.IWebContext interface
What am I missing here, why is my style.css not applied to template.html?
I had same problems and I was also trying to use thymeleaf template resolver for pdf generation. I did lots research on thymeleaf and spring framework, I tried WebContext, I tried HttpServletRequest, I tried some of Spring Thymeleaf integration solutions it was not working either. So I think it was not syntax error, and I finally end up with using absolute path instead of relative.
Url for reference
Here the reason with my assumption, lets say our resources are served on localhost:8080/myapp/css/style.css. And the relative path to request resource is really ups to what context it relatives to.
For eaxmple a normal thymeleaf model Veiw, which return as html pages on browser for client, so the context in that case would be the request hostname, port and application context(eg: localhost:8080/myapp). And relative path will be based on that. So if relative path is /css/style.css, context + relative path will result to be localhost:8080/myapp/css/style.css
Unlike web context, in our case, offline template is on server backend, so the context I assume would be the server running context, which would be the local server path + appcontext(eg: D:/myServer/apps/myapp), relative path /css/style.css on this would be D:/myServer/apps/myapp/css/style.css, this is not make sense. In order to use static resources, I have to pass it's absolute path.
I started use :
<link rel="stylesheet" type="text/css" th:href="#{http://localhost:8080/myapp/css/style.css}"/>
It's working fine but what if there are multiple host names or server is running on a proxy, then this is going to be a hard coded solution. It's better to know what is the real base url the user is requesting. So we can't really get rid off HttpSevletRequest.
Here is my code:
1.Config resource handler:
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("classpath:/css/")
.setCachePeriod(31556926);
}
Get base url from HttpServletRequest, you can inject it in method or autowired in your service class, or get from RequestContextHolder. I write this in my Service class:
private static String getCurrentBaseUrl() {
ServletRequestAttributes sra = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest req = sra.getRequest();
return req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath();
}
This is the place I use template engine in my class:
Context context = new Context();
context.setVariable("variales", variables);
context.setVariable("baseUrl", getCurrentBaseUrl());
String content = springTemplateEngine.process("myTemplate",context);
In my template, I use absolute css url like this:
<link type="stylesheet" th:src="#{|${baseUrl}/css/style.css|}" />
Syntax looks fine so the problem is not with the syntax.
Also you cannot use #{...} syntax without an IWebContext interface so You are getting this exception.
I had a similar problem - my css was not applied to my template page.
My problem was that the css file was in css sass format
.table
margin: 0 0 40px 0
when I convert it to the normal css format like
.table {
margin: 0 0 40px 0;
}
it worked
I solved this problem by changing the path structure in href. I had the same directory structure as you (html files are in templates doc, css files are in static doc).
<head>
<title>Spring Boot and Thymeleaf Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
</head>
It might help you to apply css to your html page.
I found a lazy man's way of taking care of this. It works, with a very simple approach. The 'inserted' fragment is just a CSS style tag in the body of a simple HTML document. I place this in the HEAD of my target file, right where I would have put the LINK REL tag:
<th:block th:insert="std-reports/std-reports-css-fragment.html :: style"></th:block>
Related
Thymeleaf custom dialect with fragments
In my Spring Boot 2 project I have Thymeleaf fragments to generate form elements. For example: <input th:replace="component/admin_form :: text (formElement = ${vm.getElementStatus()}, class='css-class-a'))" /> The code above generates a complex div block with label, input field and error block. I would like to simplify this syntax. My idea was to create a custom dialect with a custom tag and write this: <admin-form:text value="${vm.getElementLastName()}" class="css-class-a"/> The second one is easier to read, it clearly indicates for the designers that this is a special element. Besides this, it would be easier to change themes as I only need to change the concrete fragment location in the tag processor and not hundreds of th:replace value. It is also important that I don't want to build the complex html layout in the tag processor just want to somehow import a fragment. So designers can modify the html fragment without java code changes. I was able to create the custom dialect and create the custom tag that generates a html block: #Override protected void doProcess(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structureHandler) { final IModelFactory modelFactory = context.getModelFactory(); final IModel model = modelFactory.createModel(); model.add(modelFactory.createOpenElementTag("div", "class", "test")); model.add(modelFactory.createText("This is my custom element")); model.add(modelFactory.createCloseElementTag("div")); structureHandler.replaceWith(model, false); } But I don't know how to import a fragment in my custom tag. Is it possible anyhow?
The doProcess of your <admin-form:text> tag could create a dummy tag with a th:replace attribute that includes the "complex input" fragment: Map<String, String> attributes = new HashMap<>(); attributes.put("th:replace", "/pathToMyFragment/complexInput::myfragname"); IOpenElementTag newTag = modelFactory.createOpenElementTag("div", attributes, AttributeValueQuotes.DOUBLE, false); model.replace(0, newTag); or something similar (take care of closing the tag and so on). The result would be to replace <admin-form:text/> with <div th:replace="/pathToMyFragment/complexInput::myfragname"></div> which in turn would be processed into the final HTML. If you need to keep the original tag attributes (like class="css-class-a") you can get them from the original tag with model.get(0).getAttributeMap() and add them as a local variable with structureHandler.setLocalVariable("originalAttributes", theAttributeMap); to use in the final fragment, for example.
Css fail with URL template Spring Boot
I have very werid situation. I have a controller with two methods: #RequestMapping("/gameDetails") public String gameDetails1(Model model){ model.addAttribute("game",gameService.findById(1L)); return "gameDetails"; } #RequestMapping("/gameDetails/{id}") public String gameDetails2(#PathVariable("id") Long id, Model model){ model.addAttribute("game",gameService.findById(1L)); return "gameDetails"; } When I open /gameDetails URL page is OK, but when I use /gameDetails/1 whole page is messy, like there's no CSS at all. Im using html pages with Thymeleaf. I tried with style tags inside html and with external CSS loaded with: <link rel="stylesheet" href="../static/css/gameDetails.css" th:href="#{/css/gameDetails.css}" /> No success. Do you have any idea what is wrong? How is this even possible?
How to add JS and CSS to all content parts in an Orchard module
I am writing a module for Orchard CMS 1.8.1 I would like to add custom styles to all content parts that I have written for the module. I need these to work regardless of the theme chosen by the website admins. I could add links to the CSS and JS files in every view file for every content part - but that seems messy and prone to future bugs - what's the best way to have a single file that loads up the styles needed for all my content parts? Should I provide a different Content.cshtml that includes the links? This also seems like it could be problematic if the admins need their own control over the main Content.cshtml Many thanks
Handler should do the trick, I wrote this from the top of my head so not sure if it really works. First create ResourceManifest.cs and define your stylesheets and scripts public class ResourceManifest : IResourceManifestProvider { public void BuildManifests(ResourceManifestBuilder builder) { var manifest = builder.Add(); manifest.DefineStyle("MyStylesheet").SetUrl("mystylesheet.min.css", "mystylesheet.css").SetVersion("1.0.0"); manifest.DefineScript("MyScript").SetUrl("myscript.min.js", "myscript.js").SetVersion("1.0.0"); } } Then it should be enough to create content handler and override the BuildDisplayShape public class MyResourceHandler : ContentHandler { private readonly Work<IResourceManager> _resourceManager; public MyResourceHandler(Work<IResourceManager> resourceManager) { _resourceManager = resourceManager; } protected override void BuildDisplayShape(BuildDisplayContext context) { if (context.DisplayType == "Detail" && context.ContentItem.Has(typeof(MyPart))) { this._resourceManager.Value.Require("stylesheet", "MyStylesheet"); this._resourceManager.Value.Require("script", "MyScript"); } base.BuildDisplayShape(context); } } Adjust the IF as necessary. And let me know if it works ;) Beauty of using ResourceManifest with versioning is that anyone can replace your stylesheets/javascript with their own just by defining style in their own ResourceManifest (module/theme) with same name and higher version number and don't have to touch any original files.
Disable Url Encoding on Asp.Net HyperLink Control in Header tag
I am attempting to dynamically add a element to the html head tag in ASP.Net. Here is my code in the master page: public string LinkConincal { get { return Canonical.Href; } set { Canonical.Attributes["href"] = value; } } I use this master page property on each page and set the value to the appropriate link. My problem is if there is a & character in the url it is being encoded (&=>&) and the link becomes invalid. To see an example of this, on my page www.kwyps.com/topic.aspx?t=11&p=1 it is being displayed as <link id="Canonical" rel="canonical" href="http://www.kwyps.com/topic.aspx?t=11&p=1" /> instead of what I want: <link id="Canonical" rel="canonical" href="http://www.kwyps.com/topic.aspx?t=11&p=1" /> How do I disable the Url Encoding? Or is this valid? I'm trying to do this for SEO purposes.
It's not urlencoding its HTML/XML-encoding and is probably both valid, depending what kind of html standard you define. If you want to force your output you can use <%=YourCanonical%> in the aspx/whatever and then set it in the code via public string YourCanonical = "http:/..."
ASP.NET MVC URL auto-resolution in CSS files
In normal WebForms scenario, any root-relative URLs (e.g. ~/folder/file.txt) inside CSS files such as: .form { background-image: url(~/Content/Images/form_bg.gif); } will automatically get resolved during runtime if I specify <head runat="server"> In the referencing page. However, that is no longer happening on an ASP.NET MVC Beta1 website. Is there a way I could enable this functionality without resorting to hacks or CSS-loader file? Like maybe HttpModules or something? Or am I not desigining my website correctly? What is supposed to be a good design? Since original ASP.NET WebForms already has this feature, I'd prefer to utilize any existing functionality if possible. But I don't have much clue. This web application will be deployed in several environments where the ~ root folder might not be obvious. EDIT: I mean the url in the file's CONTENT not the file's url itself.
I would not bother with the auto-root-finding ~ character. I understand that you want the same solution to work where the root directory differs between deployments, but within the CSS document you shouldn't have any problems using relative paths. The paths in the CSS document (to the image URL in your example) will always be relative to the location of the CSS file regardless of the path of any page that loads that CSS file. So if your images are in ~/Content/Images and your stylesheets are in ~/Content/Stylesheets, you'll always be able to use background-image: url(../Images/form_bg.gif); and it will work regardless of the location of the page that loads the stylesheet. Is there a reason this wouldn't work?
One trick I have used in the past, was to actually make my CSS file have a .ASPX extension, and set the ContentType property in the page signature: <%# Page Language="C#" ContentType="text/css" %> body { margin: 0; padding: 0; background: #C32605 url(<%= ResolveUrl("~/Content/themes/base/images/BodyBackground.png") %>) repeat-x; font-family: Verdana, Arial, sans-serif; font-size: small; color: #d7f9ff; } This will ensure that the CSS file goes through the ASP.NET framework, and replaces the server side code with your relative path.
Here are some resources on implementing IHttpModule to intercept web requests to your app... Write/adapt one to check for filetype (e.g. pseudocode: if (request ends with ".css") ...) then use a regular expression to replace all instances of "~/" with System.Web.VirtualPathUtility.ToAbsolute("~/") I don't know what this will do to performance, running every request through this kind of a filter, but you can probably fiddle with your web.config file and/or your MVC URL routes to funnel all .css requests through this kind of a filter while skipping past it for other files. Come to think of it, you can probably achieve the same effect inside an ASP.NET MVC app by pointing all your CSS refrences at a special controller.action that performs this kind of preprocessing for you. i doubt that would be as performant as an IHttpModule though.
If you're trying to parse the ~/ out of any file, including text files, javascript, etc, you can write a handler that assigns a filter to it and you can use that to search for those paths... for example... public class StringParsingFilter : MemoryStream { public Stream OriginalStream { get { return this.m_OriginalStream; } set { this.m_OriginalStream = value; } } private System.IO.Stream m_OriginalStream; public StringParsingFilter() : base() { this.m_OriginalStream = null; } public override void Flush() { this.m_OriginalStream.Flush(); } public override void Write(byte[] buffer, int offset, int count) { //otherwise, parse for the correct content string value = System.Text.Encoding.Default.GetString(buffer); string contentType = HttpContext.Current.Response.ContentType; //Do any parsing here ... //write the new bytes to the stream byte[] bytes = System.Text.Encoding.Default.GetBytes(value); this.m_OriginalStream.Write(bytes, offset, count + (bytes.Length - buffer.Length)); } } And you'll write a custom handler to know when to assign this filter... like the following... public class FilterControlModule : IHttpModule { public void Init(HttpApplication context) { HttpApplication oAppContext = context; oAppContext.BeginRequest += new EventHandler(_HandleSettingFilter); } private void _HandleSettingFilter(object sender, EventArgs e) { //You might check the file at this part to make sure //it is a file type you want to parse //if (!CurrentFile.isStyleSheet()) { return; } ... //assign the new filter StringParsingFilter filter = new StringParsingFilter(); filter.OriginalStream = HttpContext.Current.Response.Filter; HttpContext.Current.Response.Filter = (Stream)filter; } } It may have actually been easier just to say "look up IHttpModules" but this is some code that I've used to parse files for paths other than ASP.net files. You'll also have to change some things in your IIS settings to allow the files to be parsed by setting the ASP.net ISAPI to be a wildcard for all of the files that get handled. You can see more at this website, if you're using IIS6 that is... You can also use this to modify any file types so you could assign some filters for images, some for javascript or stylesheets or ... really anything...
You could use an URL Rewriter to fix the URL as the request comes in, though I am not so sure it is so much elegant as a hack in this case.
I created a PathHelper util class that gives me all the paths I need. For example <link href="<%=PathHelper.CssUrl("FormulaIndex.css")%>" rel="Stylesheet" type="text/css"/> Gives me the correct full url with the help of System.Web.VirtualPathUtility.ToAbsolute() and my own convention (content/css/yourFile.css). I did the same for js, xml, t9n, pics... Its central, reusable and now I only had to change one line to catch the move of the scripts folder from content/js to Scripts in all my websites and pages. A moronic move if you ask me, but it's reality in the current beta :(