I have encountered a problem with the Yew library's component mechanism. If I include any other html code in the macro for the main model's html macro, the compiler complains that "only one root html element allowed".
My structure is as follows:
main.rs
impl Component for Model {
// ...
fn view(&self) -> Html<Self> {
html! {
<Navigation />
<p>{ "Hello World!" }</p>
}
}
}
components/navigation.rs
impl Component for Navigation {
// ...
fn view(&self) -> Html<Self> {
html! {
<nav class=("uk-navbar-container","uk-padding","uk-padding-remove-bottom","uk-padding-remove-top"), uk-navbar="">
// ...
</nav>
}
}
}
I am suspecting the html macro is adding a -tag or the whole index.html around the html tags, thus causing the "double" html tag. However, how can I avoid this or what did I miss when using components?
As mentioned before you need to wrap it. Yew supports JSX, you can do it like this:
impl Component for Model {
// ...
fn view(&self) -> Html<Self> {
html! {
<>
<Navigation />
<p>{ "Hello World!" }</p>
</>
}
}
}
I prefer this way as it doesn't add any additional HTML to the rendered document. A <div> could eventually break existing CSS or JS. But if you write a completely new component it's also a good idea to always have only one wrapping HTML element per component (=use a div).
The compiler complaint is about the yew html! macro must have one element enclosing everything.
So the fix for the error is to simply add a wrapper element like so:
main.rs
impl Component for Model {
// ...
fn view(&self) -> Html<Self> {
html! {
<div>
<Navigation />
<p>{ "Hello World!" }</p>
</div>
}
}
}
It doesn't have to be a div, it can be any html element.
Related
I have a question about how to use ngIF in HTML code to choose different selector.
I show my code as follow:
in HTML Code:
<a class="style" (click)=clickFunction() (keyup.Enter)=ke()>
<div>
content
</div>
</a>
this is html is in a shared component,
I want to use this shared component into the other component with input parameter noClickAndKeyEnter=true |false
so in this case I should change html code as follow:
<a class="stype" (click)="noClickAndKeyEnter ? clickFunction(): ''" (keyup.Enter)="noClickAndKeyEnter ? ke() :''">
<div>
content
</div>
</a>
My question is, is there also the easy way to resolve my question, that I do not write all place with noClickAndKeyEnter ? :
any solutions?
There are multiple ways you can do this. The simplest could be:
clickFunction(){
if(this.noClickAndKeyEnter){
// execute code here
} else {
// just return or whatever you want here
}
}
You can use the callback pattern!
ts
conditionallyExecute(callback: Function, ...params) {
if(this.noClickAndKeyEnter) {
callback(params)
}
}
html
<a class="stype" (click)="conditionallyExecute(clickFunction, 1, 2)" (keyup.Enter)="conditionallyExecute(ke)">
<div>
content
</div>
</a>
Just a simple guard in your functions
clickFunction() {
if (!this.noClickAndKeyEnter) return;
...
}
ke() {
if (!this.noClickAndKeyEnter) return;
...
}
But by the name of noClickAndKeyEnter I think you might have the true / false values backwards.
Here is what I want to do:
#if (condition)
{
<div class="test">
}
<span class="test2">...</span>
#if (condition)
{
</div>
}
This does not work because the Blazor compiler thinks the div is never closed.
So I need to do this:
#if (condition)
{
<div class="test">
<span class="test2">...</span>
</div>
}
else
{
<span class="test2">...</span>
}
It works fine, but I have a big code redundancy with the span.
How can I do this properly?
Please note div and span are examples. In reality, my code is bigger.
Thanks
What you're seeing is really a Razor syntax issue rather than specifically a Blazor issue. This question and answer cover it well.
So, you can do what you're trying to do in the first example, but there are also other ways of solving that issue, at least one of which is Blazor specific (there are more no doubt):
Make the class conditional
Rather than trying to not render the div, you could make the class itself conditional.
So in the code section of your page you could declare a property:
#code {
string myClass = "";
protected override void OnInitialized()
{
if (condition)
{
myClass = "whatever";
}
}
}
And then use that in your razor:
<div class='#myClass'>
<span class="test2">...</span>
</div>
That way the span is only on the page once.
Split the common code into a separate component
Another approach is to make the common part (the span in this case) into a separate component and then render that component conditionally:
#if (condition)
{
<div class="test">
<YourComponent />
</div>
}
else
{
<YourComponent />
}
That's probably overkill for the span in your example, but makes more sense where the new component would be replacing multiple lines of code.
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
I have a large chunk of HTML in an ng-repeat that for certain elements has a container element and for others it does not. I'm currently achieving this with two ng-ifs:
<strike ng-if="elem.flag">
… <!-- several lines of directives handling other branching cases -->
</strike>
<div ng-if="!elem.flag">
… <!-- those same several lines copied-and-pasted -->
</div>
While this works, it means I have to remember copy-and-paste any edits, which is not only inelegant but also prone to bugs. Ideally, I could DRY this up with something like the following (inspired by ng-class syntax):
<ng-element="{'strike':flag, 'div':(!flag)}">
… <!-- lots of code just once! -->
</ng-element>
Is there any way to achieve a similarly non-repetitive solution for this case?
You can make such directive yourself.
You can use ng-include to include the same content into both elements.
Assuming the effect you desire is to have the text within your tag be striked through based on the condition of the elem.flag:
You could simply use the ng-class as follows
angular.module('ngClassExample', [])
.controller('elemController', Controller1);
function Controller1() {
vm = this;
vm.flag = true;
vm.clickItem = clickItem
function clickItem() {
// Toggle the flag
vm.flag = !vm.flag;
};
}
.strikethrough{
text-decoration: line-through
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='ngClassExample' ng-controller="elemController as elem">
<div ng-class="{strikethrough: elem.flag}" ng-click="elem.clickItem()">
element content should be sticked through: {{elem.flag}}
</div>
</div>
You can do it with a directive
module.directive('myFlag', function() {
var tmpl1 = '<strike>...</strike>';
var tmpl2 = '<div>...</div>';
return {
scope: {
myFlag: '='
},
link: function(scope, element) {
element.html(''); // empty element
if (scope.myFlag) {
element.append(tmpl1);
} else {
element.append(tmpl2);
}
}
};
});
And you just use it like:
<div ng-repeat="item in list" my-flag="item.flag"></div>
You could create a directive which will transclude the content based on condition. For tranclusion you could use ng-transclude drirective, in directive template. Also you need to set transclude: true.
HTML
<my-directive ng-attr-element="{{elem.flag ? 'strike': 'div'}}">
<div> Common content</div>
</my-directive>
Directive
app.directive('myDirective', function($parse, $interpolate) {
return {
transclude: true,
replace: false, //will replace the directive element with directive template
template: function(element, attrs) {
//this seems hacky statement
var result = $interpolate(attrs.element)(element.parent().scope);
var html = '<'+ result + ' ng-transclude></'+result+'>';
return html;
}
}
})
Demo Plunkr
You can also use ng-transclude :
Create your directive :
<container-directive strike="flag">
<!-- your html here-->
</container-directive>
Then in your directive do something like :
<strike ng-if="strike">
<ng-transclude></ng-transclude>
</strike>
<div ng-if="!strike">
<ng-transclude></ng-transclude>
</div>
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