I'm trying to do some alchemy here: I have a Blazor Wasm application that I try (for some reasons) to encapsulate into an HTML Custom Element.
So far I understood that the script _framework/blazor.webassembly.js is the bootstrap of the application.
However, if I put that script into the shadow root of my HTML Custom Element, the script seems executed, breakpoints are reached, but nothing related to the Blazor application happens: the dotnet.wasm is not loaded, no message, no error is shown.
So the question is: did someone tried to encapsulate a Blazor Wasm application into an HTML Custom Element? How can it be done, if it can?
Edit:
Here is the resulting DOM that I have so far:
<html lang="en">
<head>
<!-- Usual things omitted -->
</head>
<body>
<my-custom-element>
#shadowRoot
<div id="app"></div>
<script src="_framework/blazor.webassembly.js" type="text/javascript"></script>
</my-custom-element>
<!-- The shadowRoot and its content is generated by the custom element declared in this script -->
<script src="importMyCustomElement.js" type="text/javascript"></script>
</body>
</html>
A bit late, but currently this is now possible. Blazor now supports registering components that can be instantiated by Javascript. There's also experimental support for registering them as custom elements.
Here's the recent devblog for it
https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-rc-1/#blazor-custom-elements
You can get started by installing the pre-release nuget package
Microsoft.AspNetCore.Components.CustomElements
Then instead of adding a root component, you can do this instead.
// WASM sample
builder.RootComponents.RegisterAsCustomElement<Counter>("my-blazor-counter");
// Server-side sample
builder.Services.AddServerSideBlazor(options =>
{
options.RootComponents.RegisterAsCustomElement<Counter>("my-blazor-counter");
});
Add the following scripts on your index
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
With that you can now use those components as custom elements, which can be used either in another SPA framework or just plain html.
// Plain usage
<my-blazor-counter increment="1"></my-blazor-counter>
// React
let count = 1;
<my-blazor-counter increment={count}></my-blazor-counter>
This works in both server-side, and webassembly. It can also be a neat way of creating real-time web-components (e.g chat box) using Server side Blazor without having to deal with SignalR.
I am investigating this as well. Looks like you are missing <div id="app">
element. Notice the id="app" this is what Blazor looks for by default (see Program.cs of Blazor Webassembly app).
I'm trying to set up a .NET core project using Razor Pages and include vueJs inside the razor page for all my logic.
Something like this:
#{
ViewData["Title"] = "VueJs With Razor";
}
<h2>#ViewData["Title"].</h2>
<div id="app">
<span>{{ message }}</span>
</div>
<script>
new Vue({
el: '#app',
data: {
message : 'Hello vue.js'
}
})
</script>
I have read that mixing Vue and Razor pages is a bad practice, and one should use Razor OR Vue.
Why is this?
Mixing VueJs and Razor Pages is not necessarily a bad thing, it can be great!
I use Vue with razor for non SPA pages and the two work well together. I choose to use Vue by loading it via a script tag from a CDN and and I do not leverage the use of WebPack for transpiling, I simply write my code in (gasp) ES5. I chose this approach for the following reasons.
Using Razor pages rather than a SPA aids in SEO and search engine ranking of public facing pages.
Loading Vue directly from a CDN eliminates a whole stack of Webpack centric technology from the learning curve which makes it much easier for new devs to get up to speed on the system.
The approach still provides the reactive goodness to UI development that Vue inherently brings to the table.
By keeping with the “page model” the code that delivers site functionality is logically grouped around the backend page that delivers that functionality.
Since Vue and Razor can do many of the same things, my goal for public facing pages is to use Razor to generate as close to the final html as possible, and to use Vue to add the reactiveness to the page. This delivers great SEO benefits for crawlers that index the page by parsing the HTML returned.
I realize the my usage of Vue is quite different than going the route of a SPA and WebPack and the approach often means I can't use 3rd party Vue Components without reworking the code a bit. But the approach simplifies the software architecture and delivers a lightweight reactive UI.
By using this approach Razor can be heavily leveraged to generate the initial rendering of the HTML with some tags containing vue attributes. Then after the page loads in the browser, Vue takes over and can reconfigure that page any way desired.
Obviously, this approach will not fit the needs of all developers or projects but for some use cases it's quite a nice setup.
A few more details for those interested
Since I use vue sitewide, my global _layout.aspx file is responsible for instantiating vue. Any sitewide functionality implemented in vue is implemented at this level. Many pages have page specific vue functionality, this is implemented as a mixin on that page or a mixin in a js file loaded by that page. When the _layout.aspx page instantiates Vue it does so with all the mixins that I have registered to a global mixin array. (The page pushed it's mixin on that global mixin array)
I don’t use .vue files. Any needed components are implemented either directly on the page or if they need to be used by multiple pages then they are implemented in a partial view like the one below.:
dlogViewComponent.cshtml :
#* dlog vue component template*#
<script type="text/x-template" id="dlogTemplate">
<div class="dlog" v-show="dlog.visible" v-on:click="dlog.closeBoxVisible ? close() : ''">
<div class="dlogCell">
<div class="dlogFrame" ##click.stop="" style="max-width:400px">
<i class="icon icon-close-thin-custom dlogCloseIcon" v-if="dlog.closeBoxVisible" ##click="close()"></i>
<div class="dlogCloseIconSpace" v-if="dlog.closeBoxVisible"></div>
<div class="dlogInner">
<div class="dlogTitle" style="float:left" v-text="title"></div>
<div class="clear"></div>
<div class="dlogContent">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</script>
#* Vue dlog component *#
<script type="text/javascript">
Vue.component('dlog', {
template: '#dlogTemplate',
props: { //don't mutate these!
closeBoxVisible: true,
title: 'One'
},
data: function () {
return {
dlog: { //nest the data props below dlog so I can use same names as cooresponding prop
closeBoxVisible: (typeof this.closeBoxVisible === 'undefined') ? true : (this.closeBoxVisible == 'true'),
title: (typeof this.title === 'undefined') ? '' : this.title,
visible: false
}
}
},
methods: {
//opens the dialog
open: function () {
app.hideBusy(); //just in case, no harm if not busy
this.dlog.visible = true;
var identifyingClass = this.getIdentifyingClass();
Vue.nextTick(function () {
$("." + identifyingClass).addClass("animateIn");
fx.manageDlogOnly();
});
},
//closes the dialog
close: function () {
fx.prepDlogClose();
var identifyingClass = this.getIdentifyingClass();
this.dlog.visible = false;
$("." + identifyingClass).removeClass("animateIn");
},
getIdentifyingClass: function () {
if (this.$el.classList.length > 1) {
//the last class is always our identifying css class.
return this.$el.classList[this.$el.classList.length - 1];
} else {
throw "A dialog must have an identifying class assigned to it.";
}
}
}
});
</script>
In the above, it's the Vue.component('dlog', ... part of the js that installs the component and makes it available to the page.
The vue code on the _layout.cshtml page looks something like the code below. By instantiating Vue on the _layout.cshtml which is used by the whole site, Vue is only instantiated in a single place sitewide:
_layout.cshtml :
<script type="text/javascript">
var app = new Vue({
el: '#appTemplate',
mixins: mixinArray, //The page adds it's mixin to mixinArray before this part of the layout executes.
data: {
errorMsg: '' //used sitewide for error messages
//other data used sitewide
},
methods: {
//methods that need to be available in vue sitewide, examples below:
showBusy: function (html) {
//functionality to show the user that the site is busy with an ajax request.
},
hideBusy: function () {
//functionality to hide the busy spinner and messaging
}
},
created: function () {
//this method is particularly useful for initializing data.
}
});
</script>
What I have provided here paints a pretty clear picture of this non-traditional approach and it's benefits. However, since several people asked, I also wrote a related blog post: Using VueJs with ASP.NET Razor Can Be Great!
You can do this. Sometimes you're obliged to do it, if, like us, you're migrating an existing code base and you can't convert everything at once. And as Ron C says, it works well.
If you're starting a new project, you have the luxury of choosing. Reasons for favouring an SPA and no Razor would be...
Reactivity. SPA apps generally feel (much) more reactive. Initial renders are often served from cache, before the data arrives. On first load, all resources arrive in a bundle, in one request-response. There's no, or much less, request chaining.
Workflow. Webpack, bundling and hot reloads are great. You get production builds, with minification, compilation of Vue render functions, elimination of 404 style errors, js syntax errors are trapped. The cycle from introducing an error to discovering it is greatly reduced for many errors.
SPA universe. Routing, Vuex, this really is the way of the future.
Purity. Razor and Vue do similar things at the end of the day. If you mix them, you may have a hard time keeping your head straight.
You can now also lint the VueJS templates within the Razor views:
https://www.npmjs.com/package/razor-vue-lint
Great answers and content on this question! Just to add re the OP, the official Vue documentation expressly states that you can mix and match and do what you like with Vue and that it is designed to be used incrementally so I'd say if it fits what you're trying to do then it is NOT an automatic bad practice.
I have a v0/Polymer 1 web component that I am upgrading to v1/Polymer 2. This web component dynamically builds a URI that loads an external JavaScript file. The external JavaScript file runs and loads an <iframe> into a <div> of my component. This external JS contains document.getElementById to load the <iframe> into the specified <div>.
I have searched and haven't found a way to force the <div> element to be exposed/placed in the shady DOM. I have read that if I design the component without a shadow DOM nothing will be displayed.
Is there anyway I can update this to web components v1/Polymer 2 with the external script (third party) still using document.getElementbyId to modify a <div> inside the web component?
UPDATE
I have found that I can force webcomponents to use the shadow dom using <script>window.ShadyDOM = { force: true };</script> or <script src="/bower_components/webcomponentsjs/webcomponents-lite.js" shadydom></script> however I still cannot access the element by ID and I do not want to force all other webcomponents to use the shady DOM. Still looking for possibilities.
What I had to do was put a <slot></slot> inside my Web Components template. When I declare the custom element, I have to nest a <div> inside of it like so: <custom-element><div></div></custom-element> which I am doing using this.appendChild() where this is the custom element. The <div> inside my element can now be accessed by document.getElementById() once it is assigned an id attribute.
Using https://github.com/danprince/ng-picky plugin
HTML is
<div ng-app="myApp" ng-controller="ngPicky">
<div class="category_header forum_category" style="background:{{color | toHex}};"></div>
<picker color="color"></picker>
both ng-picky.js and ng-picky.css are loaded, so is Angular. But the colour picker it is supposed to display is not shown. I guess css and js has not been called. Any help would be appreciated.
You have to declare the module ngPicky as a dependency of your app. The declaration of your app should look like the following:
angular.module('myApp', ['ngPicky']);
Except if you defined a controller named ngPicky, the following has no sense:
ng-controller="ngPicky"
I have a rails 4 application which is using the twitter-bootstrap-rails gem.
For some functionality, I need to render a view outside of the application layout since it has its own custom CSS and layout which must remain separate from bootstrap. The CSS file has been placed in the assets folder and the necessary line has been added to the assets.rb file. There is a view that's been created, which contains the document declaration, head and body.
A custom route and controller action were put in, and are working fine. However when the link is clicked to trigger this action, the page does not load the view correctly. The head still contains declarations for bootstrap files, despite them not existing in the view for this action. If the page is reloaded, it is displayed as intended.
Here is the controller code:
def qrcode
render file: 'app/views/boxes/qrcode.html.erb', layout: false
end
This behaviour is uniform across Safari, Chrome and Firefox, both running locally and on Heroku.
EDIT 2015-05-08
It turns out the bootstrap application.js is interfering with the CSS, however it is not declared in the custom layout and should not have been loaded at all.
The application.js is only declared in the application layout.
Controller method
def qrcode
#qr = RQRCode::QRCode.new("#{scan_url slug: #box.identifier}", size: 8).to_img.resize(400, 400).to_data_url
render layout: "label"
end
Label layout
Please forgive the amateurish HTML.
<div class="qrcode">
<%= image_tag #qr, id: "qr" %>
</div>
<div class="label">
From: Text Here<br>
Text Here
<br>
To: Text Here<br>
Text Here
</div>
Another update
Turns out the turbolinks.js file was causing the unusual behaviour.