Using ports with elm-app - elm

I'm trying to use ports with elm-app. Previously I used elm-live and a vanilla setup, and was able to insert ports like this:
index.html
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script>
window.addEventListener("load", function(event) {
var app = Elm.Main.fullscreen(localStorage.session || null);
app.ports.storeSession.subscribe(function(session) {
localStorage.session = session;
});
...
This worked, and elm-live seemed to embed elm.js in the <head> of index.html.
When I try to use this setup for ports with create-elm-app, however, the compiled javascript is embedded at the bottom of the <body>, so adding the <script> as I did results in this:
(index):68 Uncaught ReferenceError: Elm is not defined
at (index):68
What is the best way to embed the JS ports?

The halfzebra/create-elm-app project sets things up a little differently. You'll have to modify the src/index.js file like the example shows in the documentation on Javascript Interop
import './main.css';
import { Main } from './Main.elm';
import registerServiceWorker from './registerServiceWorker';
var app = Main.embed(document.getElementById('root'));
registerServiceWorker();
// ports related code
app.ports.windowTitle.subscribe(function(newTitle){
window.document.title = newTitle;
});

Related

Javascript Modules Error in ASP.NET Razor Pages

I just tried to do up a quick sample app for modern ASP.NET development and the Javascript is being problematic. As soon as you encounter a library which is exposed as a module (e.g. "qs"), you hit a road-block.
Originally, my code looked like this, with just script tags added to the markup and it was working fine:
#section Scripts {
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.24.0/axios.min.js"></script>
#*<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.10.2/qs.min.js"></script>*#
<script src="./js/main.js"></script>
<script>
main.msg = '#msg';
main.bootVue();
main.init();
</script>
}
But then I added qs and the following error ensued:
"ReferenceError: qs is not defined"
note: captializing the Q does work (as in Qs)
Otherwise, I believe the issue is the fact that qs is exposed as a module.
I'd be keen to learn how I can change my code so that it uses the qs library as intended.
Do I need to introduce 3rd party tooling like Requirejs or Webpack so that the browser understands modules? Or is it possible to just do this simply with raw Javascript.
This is what I have tried so far:
#section Scripts {
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.24.0/axios.min.js"></script>
<script type="module">
import { main } from "./js/main.js"
main.msg = '#msg';
main.bootVue();
main.init();
</script>
}
And in main.js, there is this:
import { qs } from "../js/qs.min.js"
...
export const main = new Main();
But I get the following js error:
The requested module '../js/qs.min.js' does not provide an export named 'qs'
Am I on the right track, or is there something I am not understanding?
The code using qs is:
sendDate: () => {
axios.post('', qs.stringify({ selectedDate: this.getEpoch(this.selectedDate) }), {
headers: {
RequestVerificationToken: this.antiForg
}}
);
},

Vue.js - Embed Swagger UI inside a Vue component?

I have a form in my Vue component which uploads the api file. Now I want to render the contents of the file like this:
I have imported swagger client library: https://github.com/swagger-api/swagger-ui.
Now, here
is an example of how you do it in a static page. But I need to do it inside a Vue component (or Quasar, specifically), so I do it like that:
Register swagger-ui inside my register components file:
<link rel="stylesheet" type="text/css" href="swagger-ui.css">
Now it is available as:
this.swaggerUI({})
anywhere in my components. Inside my component I have a div in a template to render the api file:
<template>
<q-form>here lies q-file element, submit button and other stuff</q-form>
<div id="swagger-ui"></div>
</template>
In the mentioned question he had something like:
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "https://yourserver.com/path/to/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
]
})
window.ui = ui
}
</script>
Here's the difference: first of all, no window.onload, I must render it on submit button. Then, I deal with an uploaded file stored in my model, so no URL here. Now, I don't get how to make it work with locally stored file, when I try with the remote url, it gives me:
vue.esm.js?a026:628 [Vue warn]: Error in v-on handler: "Invariant Violation: _registerComponent(...): Target container is not a DOM element."
I was getting a similar error (Target container is not a DOM element) trying to use a static swagger spec. Instead of using window.onload, I found that Vue has the mounted() function, so this Vue 3 file worked for me:
<template>
<div class="swagger" id="swagger"></div>
</template>
<script>
import SwaggerUI from 'swagger-ui';
import 'swagger-ui/dist/swagger-ui.css';
export default {
name: "Swagger",
mounted() {
const spec = require('../path/to/my/spec.json');
SwaggerUI({
spec: spec,
dom_id: '#swagger'
})
}
}
</script>
This one appeared to be a simple yet very unobvious typo: in windows.onload function:
dom_id: '#swagger-ui',
must instead be
dom_id: 'swagger-ui',
without hash sign, that's it!

How to run SystemJs module when view loads

I have a component that loads a javascript module that builds on Bootstrap.js and Jquery to automatically build a table of contents for a page based on H1,H2,... headers. The component code is as follows:
import { bindable, bindingMode, customElement, noView } from 'aurelia-framework';
#noView()
#customElement('scriptinjector')
export class ScriptInjector {
#bindable public url;
#bindable public isLocal;
#bindable public isAsync;
#bindable({ defaultBindingMode: bindingMode.oneWay }) protected scripttag;
private tagId = 'bootTOCscript';
public attached() {
if (this.url) {
this.scripttag = document.createElement('script');
if (this.isAsync) {
this.scripttag.async = true;
}
if (this.isLocal) {
System.import(this.url);
return;
} else {
this.scripttag.setAttribute('src', this.url);
}
document.body.appendChild(this.scripttag);
}
}
public detached() {
if (this.scripttag) {
this.scripttag.remove();
}
}
}
Essentially for those not familiar with Aurelia, this simply uses SystemJs to load the bootstrap-toc.js module from my app-bundle wherever I put this on my view:
<scriptinjector url="lib/bootstrap-toc.js" is-local.bind='true'></scriptinjector>
My problem is that although this works perfectly when I first load the view, subsequent visits don't generate a TOC (table of contents). I have checked that Aurelia is in fact calling System.Import each time the view is loaded, but it seems that once a module has been imported once, it is never imported again (the code from the bundle never runs a second time).
Does anyone know how I can unload/reload/reset/rerun the module when I re-enter the view?
Ok, so after days of fighting with this I have figured out an acceptable solution that keeps all the functionality of the TOC library and requires as few changes to the skeleton project and the target library as I could manage. Forget the script injector above.
In the index.html, do as follows:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Holdings Manager</title>
<!--The FontAwesome version is locked at 4.6.3 in the package.json file to keep this from breaking.-->
<link rel="stylesheet" href="jspm_packages/npm/font-awesome#4.6.3/css/font-awesome.min.css">
<link rel="stylesheet" href="styles/styles.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body aurelia-app="main" data-spy="scroll" data-target="#toc">
<div class="splash">
<div class="message">Holdings Manager</div>
<i class="fa fa-spinner fa-spin"></i>
</div>
<!-- The bluebird version is locked at 4.6.3 in the package.json file to keep this from breaking -->
<!-- We include bluebird to bypass Edge's very slow Native Promise implementation. The Edge team -->
<!-- has fixed the issues with their implementation with these fixes being distributed with the -->
<!-- Windows 10 Anniversary Update on 2 August 2016. Once that update has pushed out, you may -->
<!-- consider removing bluebird from your project and simply using native promises if you do -->
<!-- not need to support Internet Explorer. -->
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script src="jspm_packages/npm/bluebird#3.4.1/js/browser/bluebird.min.js"></script>
<script src="jspm_packages/npm/jquery#2.2.4/dist/jquery.min.js"></script>
<script src="jspm_packages/github/twbs/bootstrap#3.3.7/js/bootstrap.min.js"></script>
<script>
System.import('core-js').then(function() {
return System.import('polymer/mutationobservers');
}).then(function() {
System.import('aurelia-bootstrapper');
}).then(function() {
System.import('lib/bootstrap-toc.js');
});
</script>
</body>
</html>
This is assuming you have installed bootstrap using jspm (which brings in jquery as a dependency). This also assumes you have put the javascript library (the one you want to incorporate, bootstrap-toc in my case) in your src/lib folder and that you have configured your bundling to include js files from your source folder.
Next, if your library has a self executing anonymous function defined, you need to take that code and move it inside the 'attached' method of the viewmodel where you want the library to be applied. So in this case, I have a 'help' view with a bunch of sections/subsections that I wanted a TOC generated for, so the code looks like:
import { singleton } from 'aurelia-framework';
#singleton()
export class Help {
public attached() {
$('nav[data-toggle="toc"]').each((i, el) => {
const $nav = $(el);
window.Toc.init($nav);
});
}
}
The code inside the 'attached' method above was cut and pasted from the bootstrap-toc.js file and I removed the self-executing anonymous method.
I tried using system.import for the jquery/bootstrap libraries but that made part of the TOC functionality stop working and I have lost my patience to figure out why so those libraries are staying as script references for now.
Also, when you build the project you will get errors :
help.ts(7,7): error TS2304: Cannot find name '$'.
help.ts(9,16): error TS2339: Property 'Toc' does not exist on type 'Window'.
These do not cause problems at runtime since both $ and Toc will be defined before the view is ever instantiated. You can solve these build errors with this solution here.

How to trigger $('.button-collapse').sideNav('hide'); in vuejs 2 without jQuery?

I have successfully added the following code which provides me with the sideNav from materialise:
<v-btn-link v-side-nav:side-nav="nav" class="button-collapse btn-flat" id="btn-side-menu"><i class="material-icons">menu</i></v-btn-link>
<v-side-nav id="side-nav" class="hide-on-small">
<a v-on:click="handleNavDashboard()">Dashboard</a>
<a v-on:click="handleLogout()">Logout</a>
</v-side-nav>
and I use the following methods:
methods: {
handleLogout () {
console.log('LOGGED OUT')
this.$store.dispatch('clearAuthUser')
window.localStorage.removeItem('authUser')
this.$router.push({name: 'login'})
},
handleNavDashboard () {
console.log('GOING DASHBOARD')
console.log(this)
this.$router.push({name: 'dashboard'})
}
}
so when I am on the home page and i click Dashboard, I get the dashboard contents on the screen but the sideNav menu and the darkened background are still there. Materialise-css says you can use this function
$('.button-collapse').sideNav('hide');
to hide it progmatically but I don't have jQuery installed. How to I reset the sideNav after a nav click?
CDN
From Materialize docs:
One last thing to note is that you have to import jQuery before
importing materialize.js!
<body>
<!--Import jQuery before materialize.js-->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/materialize.min.js"></script>
</body>
NPM
Much better way, install jQuery via npm :
npm install jquery
and use webpack ProvidePlugin to make jQuery global module available in all of your files
here is sample of webpack.config.js file
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"
})
In Vue.js DOM manipulations are encapsulated inside directives, you can use conditional rendering directives v-if or v-show to make this work without using jquery:
jsFiddle example
Also check component framework Vuetify.js that provide clean, semantic and reusable components.
If you want to include jQuery in to a project which is using requires or imports, then you need to make sure it's required and not included using script tags, because it will be outside the scope of the compiled code (unless it was shimmed), so add the following to your project:
ES6 syntax:
import jQuery from 'jquery';
window.$ = window.jQuery = jQuery
ES5 Syntax:
window.$ = window.jQuery = require('jquery');
And make sure you have installed jQuery:
npm install jquery --save-dev
This puts jQuery into the global scope so it can be used site wide. The docs for that package don't really make that clear, and for some reason they don't mention that jQuery is a dependency, but looking at the code it clearly is for some of the components.
If you don't want to use JQuery and Materialize, you can use the directive :v-show="showAside" or :v-if="showAside" with a property like showAside (in data) and handle the value with a click.
There is a very quick and cheap example: https://jsfiddle.net/nosferatu79/p85rw6xz/

DRY for displaying meteor templates for different urls

I am having trouble setting up a simple website with different webpages and staying DRY.
I have everything set up so I the last fragment of the url is the name of the template that needs to be loaded in the content part of the webpage. All I want to do now is load that template in a specific location based on the url.
In any examples, they do this:
{{#if showCreateDialog}}
{{> createDialog}}
{{/if}}
{{#if showInviteDialog}}
{{> inviteDialog}}
{{/if}}
I'd like to do something along the lines of
{{> {{template_name}} }}
Sadly, that doesnt work. I tried this as well:
{{{content}}}
Template.content.content = function () {
var url_frag = Session.get("url_frag");
return Template[url_frag]();
}
This didnt work either. Please help!
Edit:
hmm. perhaps, my error is not in loading the template but in capturing the url:
var TodosRouter = Backbone.Router.extend({
routes: {
"*url": "main"
},
main: function (url) {
Session.set("url", url.split('/'))
}
});
The error I am getting arises when url_frag is undefined...
var url_frag = Session.get("url_frag");
initially, this works, but upon changing webpages, it fails...
Solved. I just left backbone out of it
Template.content.content = function () {
var url = window.location.pathname.split('/');
var url_frag = url.pop()
return Template[url_frag]();
Then in the html:
<template name="content">
{{{content}}}
</template>
You could also try the router smart package at atmosphere, which also supports complex routes and filters.
https://atmosphere.meteor.com/package/router
Install meteorite using npm install -g meteorite
Install router using mrt add router
Add {{renderPage}} to body
Tada! /login now renders {{> login}}
Read the document here: https://github.com/tmeasday/meteor-router