How to load external js script in angular 2 in runtime? - javascript

I am using Twitter digits for authentication. It needs a small .js script to be downloaded and initialize it. They recommend directly fetching the file from their server.
I have to import
<script id="digits-sdk" src="https://cdn.digits.com/1/sdk.js" async></script>
and initialize the sdk using like Digits.init({}).
I am using angular2 webpack starter template which uses webpack.
Earlier I was using systemjs, where I just map the file name to the url and import it at my component. Like this
var map = { 'Digits':'https://cdn.digits.com/1/sdk' };
then import it in angular component like import * as Digits from 'Digits';
I know there is webpack externals, but it is inconsistent.
console.log(Digits) in ngOnInit() sometime shows the correct object sometimes undefined error.

I've had some luck at this. I'm using a calendar js file and wanted to get the date result from an input to my controller. In my case it was Pickaday. First import the file as you normally would in whatever your main html file is.
<script src="/assets/js/pikaday.js"></script>
Then create a class the represents the functions you need.
export class PikadaySource {
field: any;
firstDay: number;
minDate: Date;
maxDate: Date;
yearRange: number = 1;
disableWeekends: boolean;
format: string = 'MM-DD-YYYY';
init(){};
}
The purpose of the class is to prevent the transpiler from having issues. Since the imported script is in global scope it should now be usable.

Related

Unable to call a JS function from Typescript in Angular 13

I'm trying to call the following function, LoadMultiSelect(), from one of my components because I am using a non-Angular library:
https://ibnujakaria.github.io/multiple-select-js/
This works perfectly in the console:
new MultipleSelect('#select-multiple-language', {
placeholder: 'Select Language'
})
And loads the JS component.
Later, I try adding it in Angular, but I cannot find how to.
I tried to export the JS function in two ways:
export default function LoadMultiSelect() {
new MultipleSelect('#select-multiple-language', {
placeholder: 'Select Language'
});
}
And like this:
LoadMultiSelect() {
new MultipleSelect('#select-multiple-language', {
placeholder: 'Select Language'
});
}
var multiselect = new LoadMultiSelect();
export { multiselect };
I created a file to load the exported function:
assets/js/multiselect.js
Later, I added it in my build in the scripts section from my angular.json like this:
"scripts": [
"./node_modules/multiple-select-js/dist/js/multiple-select.js",
"src/assets/js/multiselect.js"
]
And then I tried to add it in my component like this:
import LoadMultiSelect from '../../../../../assets/js/multiselect';
import LoadMultiSelect from 'src/assets/js/multiselect';
But nothing works, I get this error:
Could not find a declaration file for module
'../../../../../assets/js/multiselect'.
'/Users/fanmixco/Documents/GitHub/holma-bootstrap/src/assets/js/multiselect.js'
implicitly has an 'any' type.
Or others, any idea what I'm doing wrong?
P.S.:
Also, I tried using require, but it also failed.
I already tested previous solutions with an older version of Angular:
Unable to call javascript function from Typescript in Angular 6
How to call JavaScript functions from Typescript in Angular 5?
calling javascript function from typescript angular 2
I just tried this in my local system, with some random file like below,
export function MultipleSelect(val1, val2){
console.log('Be awesome always', val1, ' and ', val2);
}
now I import this in my component like this,
export class AppComponent {
title = 'stackoverflow-examples';
declare MultipleSelect: any;
constructor(
private fb: FormBuilder
) {
}
ngOnInit() {
import('./someRandomFile').then(randomFile=>{
randomFile.MultipleSelect('one', 'two')
});
}
}
In order for this file to be imported in the angular ts file, I must allow it in tsconfig.json by allowing the js import as given below,
"allowJs": true
see the result in the console below,
Note: If unable to load the file from node_modules, please put that in regular folder like asset and do the import as suggested

Why does fullcalendar give the error that it does not provide an export called 'default'?

I am trying to use the fullcalenar library through stimulusjs to display the calendar in a rails app, without a webpacker.
To start I just want to show the calendar, without complexity, as in the documentation, and the controller I am trying to do looks like this:
import FullCalendar from 'https://cdn.jsdelivr.net/npm/#fullcalendar/core#5.10.0/main.global.min.js'
export default class extends Controller {
static targets = ["calendar"]
connect() {
this.init()
}
init() {
this.calendar = new FullCalendar.Calendar(this.calendarTarget, {
initialView: 'dayGridMonth',
})
this.calendar.render()
}
}
<link href="https://cdn.jsdelivr.net/npm/fullcalendar#5.10.0/main.min.css" rel="stylesheet" />
<div data-controller="fullcalendar">
<div data-target="calendar"></div>
</div>
the error that it throws and that I do not understand is the following:
Failed to autoload controller: fullcalendar SyntaxError: The requested module 'blob:http://localhost:3000/1c3e6673-aecc-4144-b5d6-471fb03b5e0f' does not provide an export named 'default'
FullCalendar has two primary installation methods. You're using the one from this documentation page, which does not export ES6 modules. It's more like the old (pre-Gulp/Webpack) way of importing global variables.
If you want to import inside your Stimulus controllers, you'll either need to use Webpack or something importmaps, just like the other FullCalendar installation page says.
If you want something basic, I'd recommend importmaps (https://github.com/rails/importmap-rails). It requires very little configuration compared to Webpack, and will allow what you're trying to do.

Creating a DatePicker from CDN with React API

I am importing some React modules from CDN (that's not a requirement, I've also tried with a local build, more in the final question about it):
<script crossorigin src="https://unpkg.com/react-onclickoutside#6.9.0/dist/react-onclickoutside.min.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.0/moment.min.js" integrity="sha512-Izh34nqeeR7/nwthfeE0SI3c8uhFSnqxV0sI9TvTcXiFJkMd6fB644O64BRq2P/LA/+7eRvCw4GmLsXksyTHBg==" crossorigin="anonymous"></script>
<script crossorigin src="https://unpkg.com/react-datepicker#3.1.3/dist/react-datepicker.min.js"></script>
Then I have a script to build the React DatePicker component, this is the relevant snippet from it:
HelloWorld.Example=function()
{
var p,setCount,count,p$1,c,myDate,datePicker;
p=React$2.useState(0);
setCount=p[1];
count=p[0];
p$1=React$2.useState(new moment(new Date((c=Date.now(),DateUtil.DatePortion(c)))));
myDate=p$1[0];
datePicker=React$2.createElement(DatePicker.default,{
selected:new moment(new Date()),
onChange:p$1[1]
});
React.set_setCount(setCount);
return React$2.createElement("div",null,datePicker,React$2.createElement("p",null,(Html.textf(function($1)
{
The error that I see from the JS Console is:
react-datepicker.min.js:1 Uncaught TypeError: o is not a function
at Ee (react-datepicker.min.js:1)
when the script call ReactDOM.render.
Is there a way to understand what is o ? Maybe an import missing?
(Edit Well, looking at chrome debugger and comparing it to github, o is isValidDate, i.e. import isValidDate from "date-fns/isValid";, hence the imports from date-fns are not working from CDN )
Is there a way such that - for example - I can locally npm run build the needed module, react-datepicker, and then call the react API from my script as shown above? (a suggestion that I received was configuring my script as entry in webpack, but afaik React doesn't use webpack, though I see it is used in react-datepicker).
From React docs, I can read that
JSX is not a requirement for using React
so something like the above should be doable, in theory.
I've opened a question/issue on github react-datepicker repo (in the context of calling this component from WebSharper.React).
Is there a way such that - for example - I can locally npm run build the needed module, react-datepicker, and then call the react API from my script as shown above?
Yes, there is a well known solution!
Write an index.js as follows
import React from "react";
import DatePicker from 'react-datepicker'
import "react-datepicker/dist/react-datepicker.css";
export {ImportedComponent}
window.MyDatePicker = function MyDatePicker(props) {
console.log("props from window.MyDatePicker", props)
return React.createElement( DatePicker, props );
}
build via npm and copy the static folder from the build of your by npm run build to the SPA folder of your proj
copy
the 3 script tags from the index.html in the build into the index.html template of your proj
and
<div id="root"></div>
(of course you use a different id for your project app and
there will be nothing to render here)
in my case they are (they will be different for you)
<div id="root"></div>
<script>!function(e){function t(t){for(var n,l,p=t[0],f=t[1],i=t[2],c=0,s=[];c<p.length;c++)l=p[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(a&&a(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,p=1;p<r.length;p++){var f=r[p];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="/";var p=this.webpackJsonpcontent_npm=this.webpackJsonpcontent_npm||[],f=p.push.bind(p);p.push=t,p=p.slice();for(var i=0;i<p.length;i++)t(p[i]);var a=f;r()}([])</script>
<script src="/static/js/2.a6e4c224.chunk.js"></script>
<script src="/static/js/main.b075c560.chunk.js"></script>
Now go with
datePicker=React$1.createElement(window.MyDatePicker,{
selected:myDate,
onChange:p$1[1],
showTimeSelect: true,
});
in you SPA.js and enjoy any react component like this one from WebSharper.React!
Btw I had to pass a JS date, not a Moment date here in the selected of props, I'm not sure why, anyway, this is not relevant to the problem.
FYI, this is the F# code from WebSharper project
let myDate, setMyDate = WrapReact.UseState (DateTime.Today.JS)
let importDatePicker = JS.Eval("window.MyDatePicker") :?> React.Class
let propDP =
{
selected = myDate
onChange = setMyDate
showTimeSelect = true
}
let datePicker =
React.CreateElement( importDatePicker, propDP)
WrapReact.setCount <- setCount
div [] [
datePicker
p [] [Html.textf "You selected %s date %s time" (myDate.ToDateString()) (myDate.ToTimeString())]
Full open source project shared on github.
I think that the main problem is that WebSharper scripts are not JavaScript modules. In that case it should be immediate to import an external module or make the above SPA.js as the Webpack main entry. In fact it is well known that there are differences between <script type=module> and <script>
Module Script Execute in Strict Mode
Module Script has its Own Scope
Module Script can Import other Javascript Modules
Module Script has this as Undefined
Inline Module Script can have async Attribute
Module Script is Always Deferred
As confirmed by Adam Granicz indeed on WebSharper side:
that should be the way, yes, #Jand42 and others have been working on changing the current output to support modules and a better TS interoperability - this has been on the agenda for years, so closing it would be a good step forward
(In the meantime there are of course alternatives, e.g. flatpickr, which has bindings also for jQuery, instead of react-datepicker or pure React or F# Fable instead of WebSharper.React and so on)

Rails, webpacker: where/when to use the import statement in classes, and what's the purpose of requiring in the pack/application.js?

I'm upgrading an older application to Rails 6, which uses webpacker for all the JS asset management.
I'm using the pikaday calendar library, and have added it via yarn add pikaday, verified it shows up in packages.json and then added it to my app/javascript/packs/application.js via require("pikaday").
I have a JS class called Datepicker that I'm using to abstract the actual pikaday calendar. I'm doing this because I may someday change the datepicker implementation, and this way I'll only have to change a single class instead of update all my pikaday calls.
However, it doesn't seem to matter if I require("pikaday") in the application.js pack file or not; as long as I import Pikaday from "pikaday" in the class I'm referencing it in, it makes no difference.
Questions
I'm trying to gain understanding of what's going on.
Do I need to add require("pikaday") or import Pikaday from "pikaday" in the app/javascript/pack/application.js file? Why or why not?
I'm familiar with the principle that globals are bad and should be avoided, but is there a way to avoid having to import CLASS from "class_file" on every JS file that references it? In my example I want to use the Datepicker class in multiple places. The reason I ask is because I have 10+ classes like this, and it's a little annoying to have 10+ import statements at the top of every JS file that I want to use these in. There are certain classes that I always want access to. I've played with the webpacker ProvidePlugin functionality, but it complains that Datepicker is not a constructor, so I'm probably missing something but I don't have enough knowledge to know what.
app/javascript/custom/datepicker.js
import Pikaday from "pikaday"
export default class Datepicker {
constructor(element, options) {
if (options == null) { options = {} }
// Store DOM element for reference
this.element = element
// Do not re-run on elements that already have datepickers
if (this.element.datepicker === undefined) {
options = Object.assign({},
this.defaultOptions(),
options
)
const picker = new Pikaday(options)
// Store picker on element for reference
this.element.datepicker = picker
return picker
} else {
console.log("Datepicker already attached")
return
}
}
// Overridden by `options` in constructor
defaultOptions() {
return {
field: this.element,
format: "M/D/YYYY",
bound: true,
keyboardInput: false,
showDaysInNextAndPreviousMonths: true
}
}
}
app/javascript/packs/application.js
require("#rails/ujs").start()
require("turbolinks").start()
require("moment")
// Note: if using `#rails/ujs`, you do not need to use `jquery-ujs`.
import "jquery"
// Does not matter if I require this or not, as long as it is imported in the
// class file, I can remove this require statement and everything still works.
require("pikaday")
// StimulusJS
// Webpack's `require` looks for `controllers/index.js` by default
require("controllers")
require("custom/datepicker")
You asked a couple questions:
You only need import Pikaday from 'pikaday' in the file where you reference the imported variable, Pikaday. In this case, you only need this import in your custom datepicker module. You can remove require("pikaday") from your application.js pack file.
The reason for this is Webpack will take your application.js pack as an entry point to a dependency graph; starting there, it will recursively traverse each required/imported module, find the dependencies of those modules, and so on, until all declared modules are included in the bundle. Since you have declared import 'custom/datepicker' in the application.js pack, and the custom datepicker imports pikaday, it will get included in the bundle as dependency.
Your custom Datepicker gets compiled as an ES module (rather, Webpack's implementation of ES module), since you are using the ES module syntax export default .... This is important to the way ProvidePlugin works. From the Webpack 4 documentation of ProvidePlugin:
For importing the default export of an ES2015 module, you have to specify the default property of module.
This means your Webpack configuration for the plugin entry for Datepicker would look something like this (using the Rails Webpacker environment api):
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
const {resolve} = require('path');
environment.plugins.append('Provide', new webpack.ProvidePlugin({
Datepicker: [resolve('app/javascript/custom/datepicker'), 'default']
}))
module.exports = environment
Opinion: That said, I encourage you to make your imports explicit, e.g. import Datepicker from 'custom/datepicker' in each module where Datepicker is referenced. Even if repetitive, it will become much easier to integrate with tools like ESlint, which, with certain code editors, can provide inline feedback about errors from compilation—much easier to setup with explicit dependencies declared in each module.
I put together a working demo of Pikaday using your custom Datepicker with the ProvidePlugin here: https://github.com/rossta/rails6-webpacker-demo/commit/be3d20107c2b19baa8b9560bce05e0559f90086d

Importing javascript file for use within vue component

I am working on a project that requires using a js plugin. Now that we're using vue and we have a component to handle the plugin based logic, I need to import the js plugin file within the vue component in order to initialize the plugin.
Previously, this was handled within the markup as follows:
<script src="//api.myplugincom/widget/mykey.js
"></script>
This is what I tried, but I am getting a compile time error:
MyComponent.vue
import Vue from 'vue';
import * from '//api.myplugincom/widget/mykey.js';
export default {
data: {
My question is, what is the proper way to import this javascript file so I can use it within my vue component?
...
Include an external JavaScript file
Try including your (external) JavaScript into the mounted hook of your Vue component.
<script>
export default {
mounted() {
const plugin = document.createElement("script");
plugin.setAttribute(
"src",
"//api.myplugincom/widget/mykey.js"
);
plugin.async = true;
document.head.appendChild(plugin);
}
};
</script>
Reference: How to include a tag on a Vue component
Import a local JavaScript file
In the case that you would like to import a local JavaScript in your Vue component, you can import it this way:
MyComponent.vue
<script>
import * as mykey from '../assets/js/mykey.js'
export default {
data() {
return {
message: `Hello ${mykey.MY_CONST}!` // Hello Vue.js!
}
}
}
</script>
Suppose your project structure looks like:
src
- assets
- js
- mykey.js
- components
MyComponent.vue
And you can export variables or functions in mykey.js:
export let myVariable = {};
export const MY_CONST = 'Vue.js';
export function myFoo(a, b) {
return a + b;
}
Note: checked with Vue.js version 2.6.10
try to download this script
import * from '{path}/mykey.js'.
or import script
<script src="//api.myplugincom/widget/mykey.js"></script>
in <head>, use global variable in your component.
For scripts you bring in the browser way (i.e., with tags), they generally make some variable available globally.
For these, you don't have to import anything. They'll just be available.
If you are using something like Webstorm (or any of the related JetBrains IDEs), you can add /* global globalValueHere */ to let it know that "hey, this isn't defined in my file, but it exists." It isn't required, but it'll make the "undefined" squiggly lines go away.
For example:
/* global Vue */
is what I use when I am pulling Vue down from a CDN (instead of using it directly).
Beyond that, you just use it as you normally would.
I wanted to embed a script on my component and tried everything mentioned above, but the script contains document.write. Then I found a short article on Medium about using postscribe which was an easy fix and resolved the matter.
npm i postscribe --save
Then I was able to go from there. I disabled the useless escape from eslint and used #gist as the template's single root element id:
import postscribe from 'postscribe';
export default {
name: "MyTemplate",
mounted: function() {
postscribe(
"#gist",
/* eslint-disable-next-line */
`<script src='...'><\/script>`
);
},
The article is here for reference:
https://medium.com/#gaute.meek/how-to-add-a-script-tag-in-a-vue-component-34f57b2fe9bd
For anyone including an external JS file and having trouble accessing the jQuery prototype method(s) inside of the loaded script.
Sample projects I saw in vanilla JS, React and Angular were simply using:
$("#someId").somePlugin(options)
or
window.$("#someId").somePlugin(options)
But when I try either of those in my VueJS component I receive:
Error: _webpack_provided_window_dot$(...).somePluginis not a function
I examined the window object after the resources had loaded I was able to find the jQuery prototype method in the window.self read-only property that returns the window itself:
window.self.$("#someId").somePlugin(options)
Many examples show how to load the external JS file in VueJS but not actually using the jQuery prototype methods within the component.

Categories

Resources