Styling an external webpage with local css Xamarin forms - css

I'm getting an external page HTML code from my Backend as a string
and displaying it in a Webview in a Xamarin forms app
Now I would like to style it
I was wondering what is the most efficient way to do that?
and is it possible to style it in the same way a Xamarin Page would get styled with XAML and shared resources?
so far I tried referencing a CSS file in the shared resources, which I found out doesn't work...
htmlData = "<link rel=\"stylesheet\" type=\"text/css\"href=\"Assets\"Styles\"style.css\" />" + htmlData;
htmlSource.Html = htmlData;
myWebView.Source = htmlSource;
Update
I ended up using a custom renderer for the Webview
which worked for Android but not for IOS
here is my IOS implementation of the renderer
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace XXX.iOS.Renderers
{
public class CustomWebViewRenderer : WkWebViewRenderer
{
WKUserContentController userController;
public CustomWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
public CustomWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
userController = config.UserContentController;
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var customWebView = e.NewElement as CustomWebView;
if (e.NewElement != null)
{
string htmldata = customWebView.HTMLData;
htmldata = "<link rel=\"stylesheet\" type=\"text/css\" href=\"StyleSheet.css\" />" + htmldata;
WkWebViewRenderer wkWebViewRenderer = new WkWebViewRenderer();
NSData data = NSData.FromString(htmldata);
wkWebViewRenderer.LoadData(data,"text/html", "UTF-8",new NSUrl(""));
}
}
}
}
Note: I don't have any idea what is happening here with the IOS code, cause I have never coded in the native language

I don't know whether this is feasible for you, but you could inject the actual CSS in the HTML string and then assign the HtmlSource
var css = ReadStringFromAsset("style.css");
htmlData = InjectCssInHtml(htmlData, css);
htmlSource.Html = htmlData;
myWebView.Source = htmlSource;
Depending on how much control you have over the HTML you receive, you have several option on how to realize InjectCssInHtml
Pseudo-Markup comment
If changing the HTML is feasible, you could add an HTML comment as a pdeudo markup. This will make the code simple, but each HTML must be edited accordingly
<html>
<head>
<style>
<!-- CSS -->
</style>
...
</html>
your InjectCssInHtml then becomes
string InjectCssInHtml(string html, string css)
{
return html.Replace("<!-- CSS -->", css);
}
Without editing the HTML
If editing the HTML is not feasible, the InjectCssInHtml becomes a tad more complicated. The following is a first guess, but I think you get the idea
string InjectCssInHtml(string html, string css)
{
string codeToInject;
int indexToInject = 0;
if(ContainsStyleTag(html))
{
indexToInject = IndexOfStyleTagContent(html);
codeToInject = css;
}
else if(ContainsHeadTag(html))
{
indexToInject = IndexOfHeadTagContents(html);
codeToInject = $"<style>{css}</style>";
}
else
{
indexToInject = IndexOfHtmlTagContents(html);
codeToInject = $"<head><style>{css}</style></head>";
}
return html.Insert(indexToInject, codeToInject);
}
Surely this does not cover each possible case, but I think you get the idea. The ìf-else` could be replaced by an abstract factory generational pattern combined with the strategy behavioral pattern.
string InjectCssInHtml(string html, string css)
{
ICssInjector injector = injectorFactory.CreateInjector(html);
return injector.InjectCss(html, css);
}

Related

Asp.net MVC core custom tag helper not generate output

I implemented a custom tag helper to generate page numbers as a link. The "Process" method is triggered and the string builder has all the HTML content in it but it does not output the result on the screen. It just renders , Could anyone help, please?
<paginate page="Model.PageInfo" />
[HtmlTargetElement("paginate",
TagStructure = TagStructure.WithoutEndTag)]
public class PaginateTagHelper : TagHelper
{
public PageInfo Page { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
StringBuilder html = new StringBuilder();
html.Append("<div>");
for (int i = 1; i <= Page.TotalPages; i++)
{
var tag = new Microsoft.AspNetCore.Mvc.Rendering.TagBuilder("a");
tag.MergeAttribute("href", Page.PageUrl(i));
tag.InnerHtml.AppendHtml(i.ToString());
if (i == Page.CurrentPage)
{
tag.AddCssClass("selected");
tag.AddCssClass("btn-primary");
}
tag.AddCssClass("btn btn-default");
html.Append(GetTagContent(tag));
}
html.Append("</div>");
output.Content.SetHtmlContent(html.ToString());
}
private string GetTagContent(IHtmlContent content)
{
using (var writer = new System.IO.StringWriter())
{
content.WriteTo(writer, System.Text.Encodings.Web.HtmlEncoder.Default);
return writer.ToString();
}
}
}
Remove TagStructure = TagStructure.WithoutEndTag, self closing tags are not suitable for tags with inner content.
Self-closing TagHelpers
Many Tag Helpers can't be used as self-closing tags. Some Tag Helpers are designed to be self-closing tags. Using a Tag Helper that was not designed to be self-closing suppresses the rendered output. Self-closing a Tag Helper results in a self-closing tag in the rendered output.

css file not being found by thymeleaf

I try to do simple servlet that generates pdf file based on HTML template. I try to use Thymeleaf and FlyingSaucer, as in example
in my template.hmtl i have style delcaration as follow:
<link rel="stylesheet" type="text/css" media="all" href="style.css"/>
it never gets loaded. No error, nothing, just resulting .pdf is missing style. If I put content of style file into template.HTML it works like charm.
If I put something like this:
<link rel="stylesheet" type="text/css" media="all" href="http://localhost:8080/MY_APP/resources/style.css"/>
it works.
All my resources are under src/main/webapp/resources.
After few more hours of researching problem, this is what I've end up with.
For CSS - only solution that I've found was to just put CSS in HTML templates. Not elegant, but does the job (at least for now). Not a big issue, as I use those templates to generate pdf files.
But the same issue was with image files, but that one I was able to solve in elegant way. Here is it!
Problem was that in web container Java couldn't find files pointed in .html templates.
So I had to write custom Element Factory extending ReplacedElementFactory. here is code for that:
public class B64ImgReplacedElementFactory implements ReplacedElementFactory {
public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) {
Element e = box.getElement();
if (e == null) {
return null;
}
String nodeName = e.getNodeName();
if (nodeName.equals("img")) {
String attribute = e.getAttribute("src");
FSImage fsImage;
try {
fsImage = buildImage(attribute);
} catch (BadElementException e1) {
fsImage = null;
} catch (IOException e1) {
fsImage = null;
}
if (fsImage != null) {
if (cssWidth != -1 || cssHeight != -1) {
fsImage.scale(cssWidth, cssHeight);
}
return new ITextImageElement(fsImage);
}
}
return null;
}
protected FSImage buildImage(String srcAttr) throws IOException, BadElementException {
URL res = getClass().getClassLoader().getResource(srcAttr);
if (res != null) {
return new ITextFSImage(Image.getInstance(res));
} else {
return null;
}
}
public void remove(Element e) {
}
public void reset() {
}
#Override
public void setFormSubmissionListener(FormSubmissionListener listener) {
}
}
and use case in code generating PDF file:
ITextRenderer renderer = new ITextRenderer();
SharedContext sharedContext = renderer.getSharedContext();
sharedContext.setReplacedElementFactory(new B64ImgReplacedElementFactory());
custom element replaces catches all occurrence of 'img' nodes, and uses ClasLoader to get resource path, and based on that FSImage is returned, and that is what we need.
Hope that helps!

Xamarin Forms Load Resources on startup

I have an Xamarin Forms app with MvvmCross for Android and IOS and I would like to add a dark theme. My idea was to have to dictionaries with the ressources for either the dark or the light theme and load the one I need on startup.
I added this after I registered the dependencies in my MvxApplication:
if (Mvx.IoCProvider.Resolve<ISettingsManager>().Theme == AppTheme.Dark)
{
Application.Current.Resources.Add(new ColorsDark());
}
else
{
Application.Current.Resources.Add(new ColorsLight());
}
ColorsDark and ColorsLight are my ResourceDictionary. After that i can see the new Dictionary under Application.Current.Resources.MergedDictionaries but the controls can't find the resources as it seems. However it does work when I add it to the App.xaml
<ResourceDictionary Source="Style/ColorsDark.xaml" />
Do I have to put move that another part in the code or is that a wrong approach at all?
Personally don't like this approach at all. What i do: have a static class with all the colors, sizes etc. defined in static fields. At app startup or at app reload after changing skin just call ex: UiSettings.Init() for this ui definitions static class, like follows:
public static class UiSettings
{
public static Init()
{
if (Settings.AppSettings.Skin=="butterfly")
{
ColorButton = Color.Blue;
TitleSize= 12.0;
}
else
if (Settings.AppSettings.Skin=="yammy")
{
ColorButton = Color.Red;
if (Core.IsAndroid)
ButtonsMargin = new Thickness(0.5,0.6,0.7,0.8);
}
// else just use default unmodified field default values
}
public static Color ColorButton = Color.Green;
public static Thickness ButtonsMargin = new Thickness(0.3,0.3,0.2,0.2);
public static double TitleSize= 14.0;
}
in XAML use example:
Color= "{x:Static xam:UiSettings.ColorProgressBack}"
in code use example:
Color = UiSettings.ColorProgressBack;
UPDATE:
Remember that if you access a static class from different assemblies it is possible that you will access a fresh copy of it with default values where Init() didn't happen, if you face such case call Init() from this assembly too.
If you want something to load up when your app loads then you have to code it in App.xaml.cs
protected override void OnStart ()
{
if (Mvx.IoCProvider.Resolve<ISettingsManager>().Theme == AppTheme.Dark)
{
Application.Current.Resources.Add(new Xamarin.Forms.Style(typeof(ContentPage))
{
ApplyToDerivedTypes = true,
Setters = {
new Xamarin.Forms.Setter { Property = ContentPage.BackgroundImageProperty, Value = "bkg7.png"},
}
});
}
else
{
Application.Current.Resources.Add(new Xamarin.Forms.Style(typeof(ContentPage))
{
ApplyToDerivedTypes = true,
Setters = {
new Xamarin.Forms.Setter { Property = ContentPage.BackgroundImageProperty, Value = "bkg7.png"},
}
});
}
}
In this code I'm setting the BackgroungImage of all of my pages. Hope you'll get the idea from this code.

HtmlGenericControl("br") rendering twice

I'm adding some content to a given web page from code behind. When I want to add a break after some text, I try to do that this way:
pDoc.Controls.Add(New Label With {.Text = "whatever"})
pDoc.Controls.Add(New HtmlGenericControl("br"))
,where pDoc is the Panel in which I'm adding the content. But it adds two br tags into the final HTML.
I've avoid this behaviour this way:
pDoc.Controls.Add(New Label With {.Text = "whatever" & "<br />"})
Anyway, I'm so curious and I want to know why
pDoc.Controls.Add(New HtmlGenericControl("br"))
is acting that way. I also think my approach is not too fancy.
Regards,
Actually you can use;
pDoc.Controls.Add(new LiteralControl("<br/>"));
Whereas new HtmlGenericControl("br") adds two <br>, this will only add <br/> tag to your HTML so that you just have 1 space line.
In this picture I added those breaks with that code block.
Also similar question here: Server control behaving oddly
After some testing it looks like the reason is that HtmlGenericControl doesn't support self closing. On server side the HtmlGenericControl("br") is treated as:
<br runat="server"></br>
There is no </br> tag in HTML, so the browser shows it as there are two <br /> tags. Nice way out of this is to create HtmlGenericSelfCloseControl like this (sorry for C# code but you should have no issue with rewritting this in VB.NET):
public class HtmlGenericSelfCloseControl : HtmlGenericControl
{
public HtmlGenericSelfCloseControl()
: base()
{
}
public HtmlGenericSelfCloseControl(string tag)
: base(tag)
{
}
protected override void Render(HtmlTextWriter writer)
{
writer.Write(HtmlTextWriter.TagLeftChar + this.TagName);
Attributes.Render(writer);
writer.Write(HtmlTextWriter.SelfClosingTagEnd);
}
public override ControlCollection Controls
{
get { throw new Exception("Self closing tag can't have child controls"); }
}
public override string InnerHtml
{
get { return String.Empty; }
set { throw new Exception("Self closing tag can't have inner content"); }
}
public override string InnerText
{
get { return String.Empty; }
set { throw new Exception("Self closing tag can't have inner text"); }
}
}
And use it instead:
pDoc.Controls.Add(New Label With {.Text = "whatever"})
pDoc.Controls.Add(New HtmlGenericSelfCloseControl("br"))
As a simpler alternative (if you have reference to the Page) you can try using Page.ParseControl:
pDoc.Controls.Add(New Label With {.Text = "whatever"})
pDoc.Controls.Add(Page.ParseControl("br"))

Dynamic CSS for ASP.NET MVC?

It looks like the .NET community in general has not picked up on CSS compilers. In searching Google I've not found anything even remotely relevant.
Has anyone that is using ASP.NET MVC figured out a scheme to more intelligently generate their CSS? I'd love to be able to run my CSS through Razor for example, or for SASS to get ported over or what have you. Maybe I have a new side project on my hands :)
I'd love to be able to run my CSS through Razor
What stops you?
public class CssViewResult : PartialViewResult
{
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/css";
base.ExecuteResult(context);
}
}
public class HomeController : Controller
{
public ActionResult Index()
{
return new CssViewResult();
}
}
and in ~/Views/Home/Index.cshtml:
#{
var color = "White";
if (DateTime.Now.Hour > 18 || DateTime.Now.Hour < 8)
{
color = "Black";
}
}
.foo {
color: #color;
}
Now all that's left is to include it:
<link href="#Url.Action("index")" rel="stylesheet" type="text/css" />
You can also make the template strongly typed to a view model, write loops, ifs, inclusions, ...
A slight modification of #darin-dimitrov's answer. This adds the ability to pass in a model to the CssView:
public class CssViewResult : PartialViewResult
{
public CssViewResult()
{
}
public CssViewResult(object model)
{
if (model != null)
{
ViewData.Model = model;
}
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/css";
base.ExecuteResult(context);
}
}
and then you just consume it with:
return new CssViewResult(model);
Phil Haack has made a blog post about LessCSS and .net:
http://haacked.com/archive/2009/12/02/t4-template-for-less-css.aspx
I know this is an old thread, I am fairly new to ASP.NET.
Working with ASP.NET Core, I am trying to use #Darin Dimitrov's answer to allow me to set custom styles using data from an external API. To the OP's question, I know you said "Dynamic" but it seems as though you really want "Compiled". I am looking for "Dynamic".
It appears the technique of returning a partial view with response type text/css works pretty well for this solution. However, the syntax used here does not work for me in ASP.NET Core 3.1. Here is the solution I used:
Instead of HomeController I am using ThemeController as it is relevant to me.
In ThemeController.cs
public async Task<IActionResult> Index()
{
var colors = await _apiService.GetThemeColors();
Response.ContentType = "text/css";
return PartialView(colors);
}
In the view ~/Views/Theme/Index.cshtml
#model ThemeColors
#{
var color = Model.primaryColor;
}
.btn-primary {
background-color: #color;
}
And then in my _Layout.cshtml
<link href="#Url.Action("Index", "Theme")" rel="stylesheet" type="text/css" />
I hope this helps someone, if anyone who is more knowledgeable around this topic can point to more relevant resources that would be greatly appreciated.
I'm using ASP.NET MVC 4, Adding ContentType="text/css" to the page directive works for me.
Posting below solution hoping that might help someone, not sure if this is best way to implement.
I had to populate input elements dynamically inside one of the tables column field, so i had to set css position field dynamically while looping through data.
Here is the code snippet,
<td>
<label>Extract Values</label>
#{ int topPostn = 20; string topPs;}
<div style="position:relative">
#foreach (var extracts in ViewData["excode"] as List<ExtractCode>)
{
topPostn += 25;
topPs = (topPostn).ToString() + "px";
<span style="position: absolute; top: #topPs" >
<input class="ag-checkbox-input" type="checkbox" id="someid" value="#extracts.value" name="xyz" aria-label="Toggle Row Selection">#extracts.displayName
</span>
}
#{ topPostn = 20; topPs = "";}
</div>
<td>

Resources