Kendo PanelBar HTML string as Content - html

I am implementing a view where there are tabs (Kendo TabStrip) and inside these tabs are some accordion items (Kendo PanelBar).
I dinamically draw the tabs using a foreach, and in each tab, I also use a foreach to draw the accordion. The thing is that, the content of each accordion item is a HTML string (like: <p>Some <strong>text</strong></p>).
In chrome all work fine, but with IE8 everything goes out (because the page HTML mixes with the string HTML).
This is my code:
#(Html.Kendo().TabStrip()
.Name("tabAyuda")
.HtmlAttributes(new { style = "" })
.Animation(false)
.SelectedIndex(0)
.Items(tabAyuda =>
{
foreach (KeyValuePair<string, IList<ElementoAyuda>> accion in Model)
{
if (!string.IsNullOrWhiteSpace(accion.Key))
{
tabAyuda.Add().Text(accion.Key)
.Content(#<text>
#(Html.Kendo().PanelBar()
.Name("panelbar" + accion.Key)
.ExpandMode(PanelBarExpandMode.Single)
.Items(panelbar =>
{
foreach (ElementoAyuda elemento in accion.Value)
{
panelbar.Add()
.Text(elemento.Head)
.Content(elemento.Detail);
}
})
)
</text>);
}
}
})
)
I've also tried with this code inside .Content:
.Content(#<text>
#Html.Raw(elemento.Detail)
</text>)
But I get this error: Custom tool error: Inline markup blocks (#<p>Content</p>) cannot be nested. Only one level of inline markup is allowed.
Any advice??
Thanks in advance!

Solved, it was my fault. Some of the HTML strings had invalid syntax, but somehow in Chrome works XD

Related

How can I set the "target" attribute of <a> tags which are already "embedded" into HTML?

I am developing a website using VueJS, and Kentico Kontent as a CMS. This CMS offers the "rich text" feature, basically allowing text content to embed links and basic formatting, which gets automatically converted into HTML when served through the API.
I have no problem displaying the HTML content using the v-html directive, but I cannot think of a way to set the attributes of the inner <a> tags to _blank, so that the embedded links open new windows when clicked.
Is there any elegant way to do this without having to parse the HTML from the Front-end?
You could create a directive:
Vue.directive('links-in-new-window', {
inserted: function(el) {
const anchors = el.querySelectorAll('a')
anchors.forEach((anchor) => anchor.target = "_blank")
}
})
And just apply that to the same element you're using the v-html on:
<div class="content" v-html="content" v-links-in-new-window></div>
In vue V3 the directive would look like this:
app.directive('links-in-new-window', {
mounted: function(el) {
const anchors = el.querySelectorAll('a')
anchors.forEach((anchor) => anchor.target = "_blank")
}
})
HTML is the same, remember to use v- => v-links-in-new-window
<div class="content" v-html="content" v-links-in-new-window></div>

How to make links clickable in a chat

I have a chat on my website that reads from a JSON file and grabs each message and then displays it using Vue.js. However, my problem is that when a user posts a link, it is not contained in an anchor tag <a href=""/>. Therefore it is not clickable.
I saw this post, and I think something like this would work, however, I am not allowed to add any more dependencies to the site. Would there be a way for me to do something similar to this without adding more dependencies?
Code for displaying the message.
<p v-for="msg in messages">
<em class="plebe">
<b> [ {{msg.platform.toUpperCase()}} ]
<span style="color: red" v-if="msg.isadmin">{{msg.user.toUpperCase()}}</span>
<span style="color: #afd6f8" v-else="">{{msg.user.toUpperCase()}}</span>
</b>
</em>:
{{msg.message}}
</p>
In a situation like this, its preferred to write a custom functional component.
The reason for this is the fact that we are required to emit a complex html structure, but we have to make sure to properly protect against xss attacks (so v-html + http regex is out of the picture)
We are also going to use render functions, because render functions have the advantage to allow for javascript that generates the html, having more freedom.
<!-- chatLine.vue -->
<script>
export default {
functional: true,
render: function (createElement, context) {
// ...
},
props: {
line: {
type: String,
required: true,
},
},
};
</script>
<style>
</style>
We now need to think about how to parse the actual chat message, for this purpose, I'm going to use a regex that splits on any length of whitespace (requiring our chat urls to be surrounded with spaces, or that they are at the start or end of line).
I'm now going to make the code in the following way:
Make a list for child componenets
Use a regex to find url's inside the target string
For every url found, do:
If the match isn't at the start, place the text leading from the previous match/start inside the children
place the url inside the list of children as an <a> tag, with the proper href attribute
At the end, if we still have characters left, at them to the list of children too
return our list wrapped inside a P element
Vue.component('chat-line', {
functional: true,
// To compensate for the lack of an instance,
// we are now provided a 2nd context argument.
// https://vuejs.org/v2/guide/render-function.html#createElement-Arguments
render: function (createElement, context) {
const children = [];
let lastMatchEnd = 0;
// Todo, maybe use a better url regex, this one is made up from my head
const urlRegex = /https?:\/\/([a-zA-Z0-9.-]+(?:\/[a-zA-Z0-9.%:_()+=-]*)*(?:\?[a-zA-Z0-9.%:_+&/()=-]*)?(?:#[a-zA-Z0-9.%:()_+=-]*)?)/g;
const line = context.props.line;
let match;
while(match = urlRegex.exec(line)) {
if(match.index - lastMatchEnd > 0) {
children.push(line.substring(lastMatchEnd, match.index));
}
children.push(createElement('a', {
attrs:{
href: match[0],
}
}, match[1])); // Using capture group 1 instead of 0 to demonstrate that we can alter the text
lastMatchEnd = urlRegex.lastIndex;
}
if(lastMatchEnd < line.length) {
// line.length - lastMatchEnd
children.push(line.substring(lastMatchEnd, line.length));
}
return createElement('p', {class: 'chat-line'}, children)
},
// Props are optional
props: {
line: {
required: true,
type: String,
},
},
});
var app = new Vue({
el: '#app',
data: {
message: 'Hello <script>, visit me at http://stackoverflow.com! Also see http://example.com/?celebrate=true'
},
});
.chat-line {
/* Support enters in our demo, propably not needed in production */
white-space: pre;
}
<script src="https://unpkg.com/vue#2.0.1/dist/vue.js"></script>
<div id="app">
<p>Message:</p>
<textarea v-model="message" style="display: block; min-width: 100%;"></textarea>
<p>Output:</p>
<chat-line :line="message"></chat-line>
</div>
You can watch or write computed method for the variable having url and manupulate it to html content and then use v-html to show html content on the page
v-html

Replace element with razor textarea

I want to take a tag and replace with a #Html.textarea() razor html helper but it doesn't look as if JQuery can replace DOM elements with html helpers. How do I go about this?
using(#Html.BeginForm())
{
<a id="clickme">Edit</a>
<div>#Model.username</div>
}
How can I replace this div with #Html.Textarea ? JQuery could do it with div and input tags.
jQuery cannot replace a tag with #Html.TextArea() !
The TextArea helper method is a C# method, which gets executed when razor tries to render the view. This happens in your web server. jQuery is a client side library and anything you do with jQuery happens at client side, in your browser.
But all these helper methods ultimately generate some HTML for DOM elements. That means, you can use jQuery to manipulate visibility of that.
If you are trying to do something like an inline edit, you can use a script like this , to start with
First, render the text area along with your label div, but have it hidden initially. Also wrap the label,edit link and the hidden input inside a container div which we can use later to help with our jQuery selectors.
#using (#Html.BeginForm())
{
<div class="edit-item">
Edit
<div class="edit-label">#Model.FirstName</div>
#Html.TextAreaFor(a => a.FirstName,
new { style = "display:none;", #class = "edit-text" })
</div>
<div class="edit-item">
Edit
<div class="edit-label">#Model.UserName</div>
#Html.TextAreaFor(a => a.UserName,
new { style = "display:none;", #class = "edit-text" })
</div>
}
Now when the user clicks edit, you have to toggle the visibility of the label and hidden input and update the value of label after user done editing the value in the input element.
$(function () {
$("a[data-mode]").click(function (e) {
e.preventDefault();
var _this = $(this);
var c = _this.closest(".edit-item");
c.find(".edit-text").toggle();
c.find(".edit-label").toggle();
if (_this.attr("data-mode") === 'label') {
_this.attr("data-mode", 'edit');
_this.text("done");
} else if (_this.data("mode") === 'edit') {
c.find(".edit-label").text(c.find(".edit-text").val());
_this.text("edit");
_this.attr("data-mode", 'label');
}
});
});
This is a head start. You can optimize this code as needed.
Here is a working jsfiddle for your reference

How to add a font-awesome icon to kendo UI MVC menu?

I'm trying to add a font awesome icon into a kendo UI ASP.NET Menu. Unfortunately I can't find an example at Kendo on how to do it. The code is as follows:
#(Html.Kendo().Menu()
.Name("PreferencesMenu")
.HtmlAttributes(new { style = "width: 125px; height:900px; border:0px;" })
.Direction("down")
.Orientation(MenuOrientation.Vertical)
.Items(items =>
{
items.Add()
.Text("Account");
items.Add()
.Text("Notification")
.Items(children =>
{
children.Add().Text("Email");
});
items.Add()
.Text("Theme");
})
)
Does anyone know how I could add a font-awesome icon before the .Text("Account"); ?
This seemed to work for me with a sample project.
If you change the .Text("Account")
To this
.Text("<span class=\"fa fa-arrow-up\"></span> Account").Encoded(false)
That should then show an arrow up next to Account. (Obviously change the Font Awesome element to one that you want.
edit: I have added the following sample for you showing this working at multiple levels and adding the font's at the child level
#(Html.Kendo()
.Menu()
.Name("men")
.Items(item =>
{
item.Add()
.Text("<span class=\"glyphicons glyphicons-ok\"> </span>some item")
.Items(i =>
{
i.Add().Text("<span class=\"glyphicons glyphicons-plus\"></span> Hello").Encoded(false);
}
)
.Encoded(false);
item.Add()
.Text("<span class=\"glyphicons glyphicons-thumbs-up\"> </span>some item")
.Items(i =>
{
i.Add().Text("Hello");
})
.Encoded(false);
})
)
The reason for setting .Encoded(false) is so that the rendering engine just passes the data and assumes it is safe code to write out it is the equivalent of doing
#Html.Raw("<p> some html here</p>")
By setting it to true the system just treats the incoming text as a string and doesn't try to interpret the text and then apply any "html/javascript" recognition eg. <p>I'm a paragraph</p> if encoding is set to true would render out as <p>I'm a paragraph</p> if false would give you the I'm a paragraph as it's own paragraph and the markup would be applied to the page.

Razor HTML Conditional Output

I have a list of items I want to output as the contents of a main (the main in not included below). Each Item has 3 attributes: a Section Name, a Label and a Value. Each item is enclosed in a and everytime the Section Name changes I have to open a (and close the previous one, if any). I'm using a Razor view with this code:
#foreach (LocalStorageItem lsi in Model) {
string fld_name = "f_" + lsi.ItemName;
if (lsi.SectionName != sn) {
if (sn != "") {
Html.Raw("</fieldset>");
}
sn = lsi.SectionName;
<h2>#sn</h2>
Html.Raw("<fieldset>");
}
<div class="row">
<div class="ls_label">#lsi.ItemName</div>
<div class="ls_content" name="#fld_name" id="#fld_name">.</div>
</div>
}
#if (Model.Count != 0) {
Html.Raw("</fieldset>");
}
The problem is: each time the Section Name changes no fieldset tag (open and/or close) is generated. Where am I wrong? If I don't use Html.Raw (or #: as an alternative) the VS2010 parser signals an error.
Calling Html.Raw returns an IHtmlString; it doesn't write anything to the page.
Instead, you should write
#:</fieldset>
Using #: forces Razor to treat it as plain text, so it doesn't need to be well-formed.
However, your code can be made much cleaner by calling GroupBy and making a nested foreach loop.
I really think that the use of #: to work around such code is an abuse of that escape sequence. The problem should be addressed instead by correctly refactoring the code so that balanced tags can be easily written:
#foreach(var section in Model.GroupBy(i => i.SectionName)) {
<h2>#section.Key</h2>
<fieldset>
#foreach(LocalStorageItem lsi in section) {
string fld_name = "f_" + lsi.ItemName;
<div class="row">
<div class="ls_label">#lsi.ItemName</div>
<div class="ls_content" name="#fld_name" id="#fld_name">.</div>
</div>
}
</fieldset>
}
12 lines of code instead of 18