Blazor WebAssembly load different scripts for specific Environment - javascript

I'm currently working on a .NET Standard 2.1 Blazor WebAssembly application. I try to include or exclude JavaScript files in my index.html according to an environment variable.
The Blazor WebAssembly App is NOT Asp.NET Core hosted.
In .NET Core there are usually Environment Tag Helpers like in the following example:
<environment include="Development">
<script src="js/app.js"></script>
<script src="js/helpers.js"></script>
</environment>
<environment exclude="Development">
<script src="js/site.min.js"></script>
</environment>
As already discussed in this question Blazor WebAssembly Environment Variables, the Environment Tag Helpers are server side code and thus don't work in Blazor WASm.
Now I try to find a good solution to include/exclude JavaScript files according to the Environment variable in Blazor WebAssembly.
The first idea was, similar like for CSS, to create a component called <Scripts> to load the different script files on the index.html like this:
#using Microsoft.AspNetCore.Components.WebAssembly.Hosting
#inject IWebAssemblyHostEnvironment hostEnv
#*Check the environment value*#
#if (hostEnv.IsDevelopment())
{
<script src="js/app.js"></script>
<script src="js/helpers.js"></script>
}
else
{
<script src="js/site.min.js"></script>
}
#code {}
Unfortunately this doesn't work, because the <script> Element is not allowed to be used in a Blazor component (.razor file).
The following error occurs: The script element allows authors to include dynamic script and data blocks in their documents. The element does not represent content for the user. ... Script tags should not be placed inside components because they cannot be updated dynamically. To fix this, move the script tag to the 'index.html' file or another static location. ... https://go.microsoft.com/fwlink/?linkid=872131
How do you load different scripts according to the Environment Variable i.e. Development, Production or Staging in Blazor Webassembly?
Do you know how to solve this problem?

I wanted to add Tailwind CDN script tag just during development. I ended up using the solution below:
index.html
<script src="_framework/blazor.webassembly.js"></script>
<script>
// If localhost, add tailwind CDN (or any other script that you want)
if (window.location.hostname == 'localhost') {
var customScript = document.createElement('script');
customScript.setAttribute('src', 'https://cdn.tailwindcss.com');
document.head.appendChild(customScript);
}
</script>

Simply copy your index.html code in a .cshtml (named BlazorApp.cshtml in the following sample) in your server project and fallback to this page.
public void Configure(IApplicationBuilder app)
{
...
app.UseEndpoints(endpoints =>
{
...
endpoints.MapFallbackToPage("/BlazorApp");
}
}
And update the code with <environment> tags for your conveniance.

Please check the solution in this answer (same question as you linked above) and that seems to work.
Basically the workaround is to use this in a new component called Head.razor as per the solution:
#inject IWebAssemblyHostEnvironment hostEnv
#if (hostEnv.IsDevelopment())
{
<title>BlazorWasmApp - In Debug</title>
<link href="css/debug.css" rel="stylesheet" />
}
else
{
<title>BlazorWasmApp - Not Debug</title>
<link href="css/live.css" rel="stylesheet" />
}
New Head.razor component:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
//Add the Head to root components
builder.RootComponents.Add<Head>("head");
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}

Related

Can't call any Blockly methods in index.html even though it works in other .js files and a workspace is shown

I'm trying to create a tool where you can create HTML pages using Blockly blocks. I already have a page that shows my workspace and my self-created block. Now I want to write a script that gets the code from the workspace. Normally there is a workspaceToCode method in the Blockly library. Unfortunately I can't access any Blockly methods or really anything Blockly-related in my index.html.
I've looked up similar projects and can't seem to find any differences. I'm loading blockly_compressed.js, blocks_compressed.js and javascript_compressed.js. And because it shows me a workspace with "functioning" blocks I'm pretty sure that the paths are correct.
See below what I tried and thanks in advance for your help:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<style>
...
</style>
<script src="node_modules/blockly/blockly_compressed.js"></script>
<script src="node_modules/blockly/blocks_compressed.js"></script>
<script src="node_modules/blockly/msg/en.js"></script>
<script src="node_modules/blockly/javascript_compressed.js"></script>
</head>
<body>
...
<script src="html_blocks.js"></script>
<script src="html_generator.js"></script>
<script src="main.js"></script>
<script>
function update(event) {
var code = HtmlGenerator.workspaceToCode(workspace);
document.getElementById('HTMLCodeDiv').innerText = code;
}
workspace.addChangeListener(update);
</script>
</body>
</html>
The error it is giving is "unresolved function or method" for the workspaceToCode method as well as the addChangeListener method.
Since you haven't shown all of your code, I can't provide a precise answer to explain exactly what's gone wrong for you here, but I can say that Blockly in a classical (non-module) script tag adds itself to the window as follows:
// ...
} else { // Browser
var factoryExports = factory();
root.Blockly = factoryExports;
}
// ...
where root is window (by way of this) and factory() is the entire Blockly code. All Blockly functions are namespaced inside of the window.Blockly object, so there is no such window.workspace variable that would be created unless one of your other scripts (not shown) created this and attached it to the window.
If you open your browser console, you can type Blockly. and see the list of available properties that were imported by the scripts. The other Blockly scripts simply attach more properties to the global Blockly object that was created by the first script tag. Blockly.Workspace and Blockly.workspaceToCode are some of these properties, and you can call Blockly.inject to create a workspace.
For example,
const blocklyWorkspace = Blockly.inject("blockly-container", {
toolbox: document.getElementById("toolbox")
});
document.querySelector("button")
.addEventListener("click", e => {
const code = Blockly.JavaScript
.workspaceToCode(blocklyWorkspace);
console.log(code);
});
#blockly-container {
height: 100vh;
}
xml {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/blockly/8.0.0/blockly.min.js" integrity="sha512-m19pjKFpHlhFqUAWB49IQt7ip1P7UDKyV0k0f7UGnN8pXSLFjtvsrRcDlwRw+ZhaNeqQTwHwE9+CJgPAWUyA9Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<button>Workspace to code</button>
<div id="blockly-container"></div>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox">
<block type="text_print"></block>
<block type="text"></block>
</xml>
It strikes me as an antipattern to use node_modules in script tags like this, even though some of the Blockly examples do this. Usually, you'd use a bundler of some sort (webpack, parcel, vite, browserify, rollup, etc) to allow you to dynamically import the code using modules (example below). Or else keep your build without any local dependencies and use a CDN and take advantage of client caching (as shown above). Using node_modules directly seems like the worst of both worlds, especially without a minification build.
For example, you can use parcel to build your app for the web. A bundler makes it easy to use node_modules without specifying the paths. You can develop using modules rather than legacy UMD script tags, which help you organize the project into chunks and avoid polluting the window with shared data.
The example below is contrived for clarity, but hopefully you can extrapolate the approach (or something similar) to your project.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
#blockly-container {
height: 100vh;
}
xml {
display: none;
}
</style>
</head>
<body>
<div id="blockly-container"></div>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox">
<block type="text_print"></block>
<block type="text"></block>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="start-blocks">
<block type="text_print" id="N4+B!H6xh[=wx]z^LqGk" x="38" y="38">
<value name="TEXT">
<shadow type="text" id="~uw6Vr9}hxZS-=a(Zjt{">
<field name="TEXT">hello world</field>
</shadow>
</value>
</block>
</xml>
<script src="src/index.js" type="module"></script>
</body>
</html>
src/index.js:
import Blockly from "blockly";
import generateCode from "./generate-code";
const blocklyWorkspace = Blockly.inject("blockly-container", {
toolbox: document.getElementById("toolbox")
});
Blockly.Xml.domToWorkspace(
document.getElementById("start-blocks"),
blocklyWorkspace
);
console.log(generateCode(blocklyWorkspace));
src/generate-code.js:
import Blockly from "blockly";
export default blocklyWorkspace =>
Blockly.JavaScript
.workspaceToCode(blocklyWorkspace);
package.json:
{
"scripts": {
"start": "node node_modules/parcel/lib/cli index.html"
},
"dependencies": {
"blockly": "^8.0.3"
},
"devDependencies": {
"parcel": "^2.6.2"
}
}
Building and running:
npm i
npm start
Now, navigate to (by default) http://localhost:1234 (or whatever parcel tells you on the console) and begin developing.

How to fetch HTML and CSS files for web components when hosted on a different domain than the application itself?

I am looking for a clean solution to split web components into JS, HTML and CSS files and host them on a CDN. I try to avoid the webpack html and css-loader as they dont allow me to export my web component as a plain ES module.
The goal is to use a web component from any frontend app just by importing it from a spcified URL. Thereby seperation of concerns should be preserved. Individual files for style, markup and logic also allow for syntax highlighting.
In a local dev environment I found the following to work great:
WebComponent.js:
export default class WebComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
const style = new CSSStyleSheet();
const template = document.createElement("template");
fetch("./WebComponent.css").then((res) =>
res.text().then((css) => {
style.replaceSync(css);
this.shadowRoot.adoptedStyleSheets = [style];
})
);
fetch("./WebComponent.html").then((res) =>
res.text().then((html) => {
template.innerHTML = html;
this.shadowRoot.appendChild(template.content.cloneNode(true));
})
);
}
}
WebComponent.css:
button {
/* Some styling */
}
WebComponent.html:
<button>Custom buttom</button>
I can import the component by using browser native ES module imports:
index.html:
<!DOCTYPE html>
<html>
<body>
<web-component></web-component>
<script type="module">
import WebComponent from "./WebComponent";
customElements.define("web-component", WebComponent);
</script>
</body>
</html>
This works until I move the web component files to a different location (a google cloud storage bucket) than my index.html and import WebComponent.js from there.
<!DOCTYPE html>
<html>
<body>
<web-component></web-component>
<script type="module">
import WebComponent from "https://storage.googleapis.com/storage-bucket/WebComponent.js";
customElements.define("web-component", WebComponent);
</script>
</body>
</html>
WebComponent.js gets imported correctly but it then tries to fetch WebComponent.css and WebComponent.html from a URL relative to localhost where index.html is served. However it should fetch from a URL relative to where it is hosted (https://storage.googleapis.com/storage-bucket/).
Any ideas how something like that can be achieved? Without hard coding the url into both fetch calls. That's not an option as the url can change automatically from time to time.
You are having issue with linking resources in the JS web page for which :
local component is working
import WebComponent from "./WebComponent";
remote component is failing
import WebComponent from "URL";
It might be that for this to work you should try this :
<script type="module" src="https://storage.googleapis.com/storage-bucket/WebComponent.js">
customElements.define("web-component", WebComponent);
</script>
References :
https://cloud.google.com/storage/docs/hosting-static-website
https://www.npmjs.com/package/webcomponent?activeTab=readme
https://lit-element.polymer-project.org/guide/use
JavaScript file paths are relative to the displayed page. So the behavior you are observing is expected.
You can use a JavaScript variable with a simple js declaration like below and use this variable across whenever you assign URLs dynamically:
<script type="text/javascript">
var webComponentPath = 'https://storage.googleapis.com/storage-bucket/';
</script>

Include static files in a reusable Razor Class Library

I am trying to make a reusable class library (RCL) that I can use in several ASP.NET Core MVC projects. So far so good… until I try to include the required JavaScript in the RCL. There is little-to-no documentation about this topic. My best shot was to try this example.
But when I do, I get the following error when I build the library:
This is the project file and the structure of the library:
Any help is appreciated.
Now that I have some spare time I will answer my own question. Maybe it will useful for someone.
Finally I solved this problem using EmmbededResources without the EmbeddedFilesManifest as ianbusko pointed out in Github.
First I created an extension for the IApplicationBuilder class:
namespace Dashboard.Lib.Extensions
{
public static class IApplicationBuilderExtension
{
public static void UseDashboardScripts(this IApplicationBuilder builder)
{
var embeddedProvider = new EmbeddedFileProvider(typeof(Areas.Dashboard.ViewComponents.DashboardViewComponent)
.GetTypeInfo().Assembly, "Dashboard.Lib.Scripts");
builder.UseStaticFiles(new StaticFileOptions()
{
FileProvider = embeddedProvider,
RequestPath = new PathString("/Scripts")
});
}
}
}
Then I added the javascript files to the project file:
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateEmbeddedFilesManifest>false</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Scripts/**/**/**/*" Pack="true" />
</ItemGroup>
In the RCL view the javascript is included as follows:
#section Scripts{
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript" src="~/Scripts/pagination.js"></script>
<script type="text/javascript" src="~/Scripts/checkdigit-validator.js"></script>
<script type="text/javascript" src="~/Scripts/rut-validation.js"></script>
}
Finally in the Statup.cs in the main MVC project you just have to include the following:
app.UseStaticFiles();
app.UseDashboardScripts();

Play Framework template that is actually a JS file

I'd like to have a Play template that is a JS file (as opposed to having <script> tags inside an HTML template). The reason for this is so that the script can be cached. However, I need to create a differences in the script depending on where it's included and hoped to do this with Play's template system. I can already do so if I use embedded scripts, but those can't be cached.
I found an existing question that also asks the same thing, but the answer is totally different (different goals).
That's easy, just... create view with .js extension, i.e.: views/myDynamicScript.scala.js:
#(message: String)
alert('#message');
//Rest of your javascript...
So you can render it with Scala action as:
def myDynamicScript = Action {
Ok(views.js.myDynamicScript.render(Hello Scala!")).as("text/javascript utf-8")
}
or with Java action:
public static Result myDynamicScript() {
return ok(views.js.myDynamicScript.render("Hello Java!"));
}
Create the route to you action (probably you'll want to add some params to it):
GET /my-dynamic-script.js controllers.Application.myDynamicScript()
So you can include it in HTML templite, just like:
<script type='text/javascript' src='#routes.Application.myDynamicScript()'></script>
Optionally:
You can also render the script into your HTML doc, ie by placing this in your <head>...</head> section:
<script type='text/javascript'>
#Html(views.js.myDynamicScript.render("Find me in the head section of HTML doc!").toString())
</script>
Edit: #See also samples for other templates types

how to integrate TinyMCE and CKEditor in Meteor JS?

I am trying to use CKEditor or TinyMCE editor in my project.
So I put TinyMCE folder in meteor public folder, also put
<head>
<script type="text/javascript" src="<your installation path>/tinymce/tinymce.min.js"></script>
<script type="text/javascript">
tinymce.init({
selector: "textarea"
});
</script>
in template head tag.
However receiving following error.
Resource interpreted as Script but transferred with MIME type text/html: "http://localhost:3000/%3Cyour%20installation%20path%3E/tinymce/tinymce.min.js". (index):97
Uncaught SyntaxError: Unexpected token < tinymce.min.js:1
Uncaught ReferenceError: tinymce is not defined
How do I fix this problem? It is same to CKEditor.
Is there any other rich editor ,which I can use in Meteor JS?
First, you need to put everything from the CKEDITOR build download in the public folder. CKEDITOR comes with all sorts of stuff and references everything based on relative directories.
Your public folder should have a directory named ckeditor it should contain contain the following files and folders:
adapters
lang
plugins
skins
ckeditor.js
config.js
contents.css
styles.js
In your primary layout file reference CKEDITOR like so:
<head>
<script type="text/javascript" src="/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/ckeditor/adapters/jquery.js"></script>
</head>
In your template:
<template name="yourTemplate">
<textarea id="content" name="content"></textarea>
</template>
Finally, in the rendered function of your template:
Template.yourTemplate.rendered = function() {
$('#content').ckeditor();
};
Normally, you would say this.$('#content').ckeditor() but that doesn't work because CKEDITOR is in your public folder. As a result, you need to the global reference to the #content element.
Instead of /public folder, put your files in /client/compatibility. Then initialize it in the template you want to use it.
Template.editor.rendered = function() {
tinymce.init({
selector: 'textarea'
});
};
This was the only result searching for wysiwyg:
https://github.com/mcrider/meteor-bootstrap-wysiwyg
meteor add mcrider:bootstrap-wysiwyg
Looks a bit simpler than CKEditor or TinyMCE but maybe that's ok for your project.

Categories

Resources