Blazor dynamically createElement and appendChild - dynamic

i want to dynamically create DIV Containers via JSInterop in Blazor Webassembly. My approach was to create a CreateElement method in C# which calls createElement in javascript and returns a ElementReference as a Result. But when i run the Following code, i just get an empty object.
C# code:
public object CreateElement(ElementReference elementReference)
{
return JsRuntime.Invoke<object>("createElement",
elementReference,
DotNetObjectReference.Create(this));
}
Javascript code:
createElement(element, objectReference) {
const newDiv = document.createElement("div");
return element.appendChild(newDiv);
}

You don't need JS for that. Blazor was made so you don't have to manipulate the DOM. Create components. Manipulating the DOM this way defeats the purpose for which Blazor was made. You may want to start with some basics at https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-3.1

As #Porkopek mentioned, there's not really a good reason to do this as it more or less defeats the purpose of using Blazor to dynamically render markup.
If you absolutely have to use createElement, you should bind your JS interop code to the window object to invoke:
window.createDivElement = createElement(element, objectReference) {
const newDiv = document.createElement("div");
return element.appendChild(newDiv);
}
Side note, DotNetObjectReference.Create(this) is for calling back into C# component methods via the [JSInvokable] attribute and probably not needed here.

Related

Vue3 Web Component V-model

i would to create a web component with vue3 for manage inputs and forms.
But i have problem with two data binding.
I define the web component in my html page.
<vue-input-text id="prova_di_un_input_text" name="prova_di_un_input_text" value=''></vue-input-text>
and i hook the emit when i write
document.querySelector('#prova_di_un_input_text').addEventListener('update:value', function (e) {
console.log("input cambiato", e, e.detail)
let valore_da_aggiornare = e.detail[0].value
});
but i don't understand how can update web component value after get new value.
thanks for every support
You don't need to implement two-way data bindings in Vue (awesome, right?) yourself unless you need something specific. Just use v-model directive, here's the doc
Code example:
<vue-input-text v-model="myValue" id="prova_di_un_input_text" name="prova_di_un_input_text" />
const myValue = ref('')

VueJS 3 create/pass hyperscript to template at runtime?

I wanted to try something for performance/convenience purposes, I understand the gains will be minimal but understanding how/if/why this works would also just be helpful to learn.
I have a some custom data types (defined as classes) that are used to identify certain properties throughout my application. I want to use a static function on the type to define a display function. (stripped down) Example:
class Email extends String{
static display = (value) => {
return `<a href='mailto${value}'>${value}</a>`;
}
}
Call it like you do:
Email.display("test#test.com");
And that works in the template, so long as it’s in a v-html attribute. This is perfectly acceptable.
It’s probably important to specify I’m working with Vue-CLI and single-file components, so all that sweet hyperscript gets created at compile time.
But it got me thinking, is there a way I can pass a freshly-created hyperscript to the template at render? Preferably in a way that works in the {{mustache}} if at all possible.
I tried doing it with h but that just displays the ol’ [object Object].
class Email extends String{
static display = (value) => {
return h('a', {innerHtml: value});
}
}
Update: also tried
I thought maybe going around the Vue render functions could get the job done, but they don't seem to like document fragments either.
static display = (value) => {
var fragment = document.createDocumentFragment();
var a = document.createElement('a');
a.textContent = value;
fragment.appendChild(a);
return fragment;
}
Question
Is there a way create hyperscript at runtime and utilize it in a vue template? Bonus points if it works in {{mustache}} and v-html.
Generally component templates and render functions that use JSX or h (hyperscript) are mutually exclusive.
It's really possible to do this, in this case display is actually functional component, and it needs to be output as any other dynamic component:
setup() {
const display = (props) => {
return h(...);
};
return { display };
}
and
<component :is="display" :value="..."/>
The return of display is a hierarchy of vnode objects, they can't be used as is in v-html without being previously rendered to HTML.

Prevent DOM reuse within lit-html/lit-element

I am looking for a way to NOT reuse DOM elements within lit-html/lit-element (yes, I know, I'm turning off one of the prime features). The particular scenario is moving an existing system to lit-element/lit-html that at certain points embeds the trumbowyg WYSIWYG editor. This editor attaches itself to a <div> tag made within lit-element and modifies its own internal DOM, but of course lit-html does not know that this has happened, so it will often reuse the same <div> tag instead of creating a new one. I am looking for something similar to the vue.js key attribute (e.g., preventing Vue from aggresively reusing dom-elements)
I feel like the live() directive in lit-html should be useful for this, but that guards against reuse based on a given attribute, and I want to prevent reuse even if all attributes are identical. Thanks!
I have had similar issues with rich text editors and contenteditable - due to how templates update the DOM you don't want that to be part of a template.
You do this by adding a new element with the non-Lit DOM and then adding that to the DOM that Lit does manage:
class TrumbowygEditor
extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const div = document.createElement('div');
shadow.appendChild(div);
const style = document.createElement('style');
// Add CSS required
shadow.appendChild(style);
$(div).trumbowyg(); //init
}
}
customElements.define('trumbowyg-editor', TrumbowygEditor);
As this is running in a custom element's shadow DOM Lit won't touch it, you can do:
html`
<div>Lit managed DOM</div>
<trumbowyg-editor></trumbowyg-editor>`;
However, you will have to implement properties and events on TrumbowygEditor to add everything you want to pass to or get from the nested jQuery component.
You can add the scripts with import if you can get module versions of jQuery/Trumbowyg (or your build tools support it) or you can add <script> tags to your component, add fallback loading DOM content in the constructor, and then on the load event of the <script> call the $(div).trumbowyg() to init the component.
While messier and more work I'd recommend the latter as both components are large and (thanks to jQuery being built on assumptions that are now 15 years old) need to load synchronously (<script async or <script defer don't work). Especially on slower connections Lit will be ready long before jQuery/Trumbowyg have loaded in, so you want <trumbowyg-editor> to look good (show spinner, layout in the right amount of space etc) while that's happening.
You write that you attach the external library directly to an element managed by lit-html. It sounds like you're doing essentially this:
render(html`<section><div id=target></div></section>`, document.body)
external_lib.render_to(document.querySelector("#target"))
If this is what you do instead try to create your own div, let the external lib render to that div, and finally attach that div to lit-html:
let target_div = document.createElement('div')
render(html`<section>${div}</section>`, document.body)
external_lib.render_to(target_div)
The most up-to-date answer to this problem is to use Lit's built-in keyed directive. This scenario is exactly what it's for:
https://lit.dev/docs/templates/directives/#keyed
Associates a renderable value with a unique key. When the key changes, the previous DOM is removed and disposed before rendering the next value, even if the value—such as a template—is the same.
#customElement('my-element')
class MyElement extends LitElement {
#property()
userId: string = '';
render() {
return html`
<div>
${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
</div>`;
}
}

Making small components on runtime

I am having a problem working with JQuery DataTable. I had to use that plugin since I had no other choice allowed due to my project requirements.
So the problem is that, I am adding rows to DataTable and in the row there's a column with button HTML tag. Now I want to bind an on click handler to the button.
dt.Rows.Add({
column_1_data,
column_2_data,
"<button #click='itsVueTime'>MyButton</button>"
});
Here dt is the DataTable's instance. Now the problem is #click won't work. I understand that its not being rendered by Vue thats why its not working.
Is there a way to bind click event in this condition?
Without knowing more context, I would recommend this way of doing it
In your component with the method you want to use, you can expose the component like this. (I use mounted, but you can use other lifecycle methods too like created)
mounted() {
window.app = this;
}
then you can use
<button onclick="app.holler()">Say Hello</button>
you can also expose just the function you want to use like so
mounted() {
window.itsVueTime = this.itsVueTime;
}

Loading jquery plugin result into Durandal view

I am using the Durandal Starter Template for mvc4. I have set the following simple View:
<section>
<h2 data-bind="html: displayName"></h2>
<h3 data-bind="html: posts"></h3>
<button data-bind="click: getrss">Get Posts</button>
<div id="rsstestid" ></div>
</section>
and ViewModel:
define(function (require) {
var http = require('durandal/http'),
app = require('durandal/app');
return {
displayName: 'This is my RssTest',
posts: ko.observable(),
activate: function () {
return;
},
getrss: function () {
$('#rsstestid').rssfeed('http://feeds.reuters.com/reuters/oddlyEnoughNews');
return;
}
};
});
As you can see, it is simply using the zRssReader plugin to load posts into a div when the 'Get Posts' button is clicked. Everything works fine, the display name is populated and the posts show up as expected.
Where I am having trouble is when I try to eliminate the button and try to load the posts at creation time. If I place the plugin call in the activate function, I get no results. I assume this is because the view is not fully loaded, so the element doesn't exist. I have two questions:
How do I delay the execution of the plugin call until the view is fully composed?
Even better, how do I load the plugin result into an the posts observable rather than using the query selector? I have tried many combinations but no luck
Thanks for your help.
EDIT** the below answer is for durandal 1.2. In durandal 2.0 viewAttached has changed to attached
Copy pasted directly from durandaljs.com
"Whenever Durandal composes, it also checks your model for a function called viewAttached. If it is present, it will call the function and pass the bound view as a parameter. This allows a controller or presenter to have direct access to the dom sub-tree to which it is bound at a point in time after it is injected into its parent.
Note: If you have set cacheViews:true then viewAttached will only be called the first time the view is shown, on the initial bind, since technically the view is only attached once. If you wish to override this behavior, then set alwaysAttachView:true on your composition binding."
--quoted from the site
There are many ways you can do it but here is just 1 quick and dirty way:
<section>
<h2 data-bind="html: displayName"></h2>
<h3 data-bind="html: posts"></h3>
<button data-bind="click: getRss">Get Posts</button>
<div id="rsstestid"></div>
</section>
and the code:
define(function (require) {
var http = require('durandal/http'),
app = require('durandal/app');
var $rsstest;
return {
displayName: 'This is my RssTest',
posts: ko.observable(),
viewAttached: function(view) {
$rssTest = $(view).find('#rsstestid');
},
getRss: function() {
$rssTest.rssfeed('http://feeds.reuters.com/reuters/oddlyEnoughNews');
}
};
});
In general, I think it's wise to refrain from directly touching UI elements from within your view model.
A good approach is to create a custom KO binding that can render the rss feed. That way, you're guaranteed that the view is in place when the binding executes. You probably want to have the feed url exposed as a property on your view model, then the custom binding can read that when it is being updated.
Custom bindings are pretty simple - if I can do it, then it must be :)
Here's a link to the KnockOut custom bindings quickstart: http://knockoutjs.com/documentation/custom-bindings.html
I too am having the same problem, I'm trying to set a css property directly on an element after the durandal view model and view are bound together. I too assume that it's not working because the view is not fully composed at the point I am setting the value.
Best I have come up with is using the viewAttached lifecycle event in durandal, which I think is the last event in the loading cycle of a durandal viewmodel, and then using setTimeout to delay the setting of the property still further.
It's a pretty rubbish workaround but it's working for now.
var viewAttached = function (view) {
var _this = this;
var picker = new jscolor.color($(view).children('.cp')[0], {
onImmediateChange: function() {
_updateCss.call(_this, this.toString());
}
});
picker.fromString(this.color());
setTimeout(function() {
_updateCss.call(_this, _this.color());
}, 1000);
};
var activate = function (data) {
system.log('activated: ' + this.selectors + ' ' + this.color());
};
var deactivate = function (isClose) {
system.log('deactivated, close:' + isClose);
};
return {
viewAttached: viewAttached,
deactivate: deactivate,
activate: activate,
color: this.color
};
I was having a similar issue with timing. On an initial page load, where a partial view was being loaded on the page I could call the viewAttached function and use jQuery to bind some elements within the partial view. The timing worked as expected
However, if I navigated to a new page, and then back to the initial page, the same viewAttached + jQuery method failed to find the elements on the page... they had not yet been attached to the dom.
As best as I have been able to determine (so far) this is related to the transition effects in the entrance.js file. I was using the default transition which causes an object to fade out and a new object to fade in. By eliminating the fadeOutTransition (setting it to zero in entrance.js) I was able to get the viewAttached function to actually be in sync with the partial views attachment.
Best guess is that while the first object is fading out, the incoming object has not yet been attached to the dom but the viewAttached method is triggered anyway.