PTwr PTwr - 4 months ago 17
HTML Question

Razor template <table> as valid pure HTML for IFrame based WYSIWYG

I have Razor templates which can be styled by client in IFrame based WYSIWYG (currently SCeditor).

Primitive constructs, like

<p>@Model.Price</p>
or
@(Model.CashOnDelivery ? "cash on delivery" : "transfer")
works fine in WYSIWYG, however problem arises with
<table>
. Modern browsers love to fix DOM, and Razor syntax between table tags is not really valid HTML, so what I have is certainly not what I see.

For example

<table border="1">
<tr>
<th>Name</th>
<th>Amount</th>
<th>Price</th>
<th>TotalPrice</th>
</tr>
@foreach (var service in Model.Services)
{<tr>
<td>@service.Name</td>
<td>@service.Amount</td>
<td>@service.ItemPrice</td>
<td>@service.TotalPrice</td>
</tr>}
</table>


when displayed WYSIWYG IFrame DOM becomes

@foreach (var service in Model.Services)
{}
<table border="1">
<tr>
<th>Name</th>
<th>Amount</th>
<th>Price</th>
<th>TotalPrice</th>
</tr>
<tr>
<td>@service.Name</td>
<td>@service.Amount</td>
<td>@service.ItemPrice</td>
<td>@service.TotalPrice</td>
</tr>
</table>


So when user hits Save, WYSIWYG gives me broken code based on fixed DOM. Chrome, Firefox and IE 10+ perform roughly same corrections.

I tried to produce some hacks, like hiding Razor in fake attributes but


  • Imbalanced html tags in source cause Razor compilation to fail

  • attributes in closing tags are not valid HTML either



Filling table with JS won't work, as templates often go straight to email. Switching from
<table>
to
<div>
would take away WYSIWYG functions from tables, so its not a good option either. I could hide table generation in a function, but again, it would prevent WYSIWYGing styles for it...

To summarize: I need to stay with Razor,
<table>
and dynamic generation of rows yet maintain WYSIWYG capabilities. Preferably not making templates horrid to user in the process. And I am all out of ideas.

Answer

Well, I managed to produce a workaround.

I changed template to

<table border="1">
    <tr>
        <th>Name</th>
        <th>Amount</th>
        <th>Price</th>
        <th>TotalPrice</th>
    </tr>
    @foreach (var service in Model.Services)
    {<tr razor-outer="foreach (var service in Model.Services)">
        <td>@service.Name</td>
        <td>@service.Amount</td>
        <td>@service.ItemPrice</td>
        <td>@service.TotalPrice</td>
    </tr>}
</table>

and then before displaying in wysiwyg simply remove text nodes before and after <tr> nodes with razor-outer attribute. By hiding code in attribute process is fully reversible and WYSIWYG friendly.

public static string PackRazor(string razor)
{
    if (string.IsNullOrWhiteSpace(razor))
    {
        return razor;
    }
    HtmlDocument doc = new HtmlDocument();

    doc.LoadHtml(razor);

    string razorOuterXpath = "//*[@razor-outer]";

    var nodes = doc.DocumentNode.SelectNodes(razorOuterXpath);
    if (nodes != null)
    {
        foreach (HtmlNode node in nodes)
        {
            HtmlAttribute razorAttr = node.Attributes["razor-outer"];
            var prev = node.PreviousSibling;
            var next = node.NextSibling;

            if (prev.NodeType == HtmlNodeType.Text && prev.InnerHtml.Contains(razorAttr.Value))
            {
                node.ParentNode.RemoveChild(prev);
            }
            if (next.NodeType == HtmlNodeType.Text && next.InnerHtml.Contains("}"))
            {
                node.ParentNode.RemoveChild(next);
            }
        }
    }

    return doc.DocumentNode.OuterHtml;
}

and before saving template razor can be restored from attribute

public static string UnpackRazor(string html)
{
    if (string.IsNullOrWhiteSpace(html))
    {
        return html;
    }
    HtmlDocument doc = new HtmlDocument();

    doc.LoadHtml(html);

    string razorOuterXpath = "//*[@razor-outer]";

    var nodes = doc.DocumentNode.SelectNodes(razorOuterXpath);
    if (nodes != null)
    {
        foreach (HtmlNode node in nodes)
        {
            HtmlAttribute razorAttr = node.Attributes["razor-outer"];
            var newNode = HtmlNode.CreateNode(html);
            node.ParentNode.InsertBefore(HtmlNode.CreateNode("@" + razorAttr.Value + "{"), node);
            node.ParentNode.InsertAfter(HtmlNode.CreateNode("}"), node);
            Console.WriteLine(razorAttr.Value);
        }
    }

    return doc.DocumentNode.OuterHtml;
}
Comments