How will I access the components in the script inside a template? - javascript

I would like reuse my html components that contains some javascript code, so for simplify I bring one simple example:
index.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<my-component></my-component>
<script src="index.js"></script>
</body>
</html>
my-component.html:
<template>
<div id="something"></div>
<script>
// It doesn't work, this here is "window"
document.getElementById("something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, {mode: "no-cors"})
.then(res => res.text())
.then(html => {
const parser = new DOMParser()
const document = parser.parseFromString(html, 'text/html')
const head = document.head
const template = head.querySelector('template')
return template
})
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.importNode(template.content, true))
}
}
return customElements.define(name, UnityComponent)
}
function loadComponent (name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template))
}
return {loadComponent}
}())
makeComponent.loadComponent("my-component", "my-component.html")
I can with this code, but it copy all variables of the script to window:
<template>
<div id="something"></div>
<style onload="templFunc.apply(this.getRootNode())"></style>
<script>
function templFunc() {
// It works
let text = "Something"
this.querySelector('#something').innerHTML = text
// but...
console.log(window.text) // => "Something"
}
</script>
</template>
It doesn't make a sense, if the script is inside the template at least should can access the elements inside the template, else the template is almost not util for the javascript, so, I can't understand the intention of use script inside the template or how to reuse the web components that use javascript, Is it wrong do this?
So, How to I access the components in the script inside a template without copy all script variables to window?

As you found out <script> inside a <template> runs in Global scope
If you use Angular, note Angular bluntly removes all <script> content from Templates.
One workaround is to add an HTML element that triggers code within Element scope.
<img src onerror="[CODE]"> is the most likely candidate:
This then can call a Global function, or run this.getRootNode().host immediatly.
<template id=scriptContainer>
<script>
console.log("script runs in Global scope!!");
function GlobalFunction(scope, marker) {
scope = scope.getRootNode().host || scope;
console.log('run', marker, 'scope:', scope);
scope.elementMethod && scope.elementMethod();
}
</script>
<img src onerror="(()=>{
this.onerror = null;// prevent endless loop if function generates an error
GlobalFunction(this,'fromIMGonerror');
})()">
</template>
<my-element id=ONE></my-element>
<my-element id=TWO></my-element>
<script>
console.log('START SCRIPT');
customElements.define('my-element',
class extends HTMLElement {
connectedCallback() {
console.log('connectedCallback', this.id);
this.attachShadow({ mode: 'open' })
.append(scriptContainer.content.cloneNode(true));
}
});
</script>
More detailed playground, including injecting SCRIPTs, at: https://jsfiddle.net/WebComponents/q0k8ts6b/

Here is the solution,
my-component:
<template>
<div id="something"></div>
<script>
makeComponent.getComponent("my-component", "something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
this.setAttribute("id", name);
shadow.appendChild(document.importNode(template.content, true));
}
}
return customElements.define(name, UnityComponent);
}
function getComponent(host, query) {
return document.getElementById(host).shadowRoot.querySelector(query);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { getComponent, loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However I think that this is not the better way, maybe I need use the events here, and pass the shadow scope to a listener that is called in the script tag in the template, but I don't know how to pass the scope to the event yet.
Up:
With events:
my-component:
<template>
<div id="something"></div>
<script>
document.addEventListener("custom-event", (e) => {
console.log(e.detail.target.shadowRoot.getElementById("date-year"));
})
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(document.importNode(template.content, true));
const event = new CustomEvent("custom-event", {'detail': {
target: this
}});
document.dispatchEvent(event);
}
}
return customElements.define(name, UnityComponent);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However, I prefer the first solution even. But if you need of nested components the first doesn't work, you need of the second.

Related

How to execute script file before another script in layout page?

Inside my layout page i wanna run a script for translate the text of some enumumerators.
My layout looks like this:
<html>
<head>
...
<script src="~/js/ViewModels/Helpers/Translation.js"></script>
<script src="~/js/ViewModels/Helpers/Enumerators.js"></script>
...
</head>
<body>
...
</body>
</html>
My translation script:
'use strict';
var jsonTranstation = null;
$(function () {
getjson();
});
const getjson = () => {
$.getJSON('/lib/translation/en-EN.json', function (data) {
jsonTranstation = data;
});
}
const Translation = (value, ViewModel) => {
let a = null;
if (jsonTranstation ) {
a = jsonTranstation[ViewModel][value];
return a;
}
return '--';
}
My Enumerators script:
'use strict'
const EnumToolBar = {
NEW: { text: Translation('New', 'EnumToolBar'), prefixIcon: 'e-add', id: 'NEW' }
}
My JSON file (en-EN.json):
{
"EnumToolBar": {
"New": "New Value"
}
}
Using EnumToolBar.NEW.text in HomePage returns '--' instead of 'New Value'.
There are any way to read first script and the respective json file before any other script?

Azure Devops(vsts) expension - How to create task attachment content

I am working on creating an extension for azure devops, which creates a custom tab and displays the result.
I uploaded the file using "##vso[task.addattachment]".
Eg: console.log('##vso[task.addattachment type=TestReport;name=MyReport;]c:/user/index.html');
I am having problem in consuming that file and displaying it on new tab, I went through the sample code provided by MS - build_release_enhancer
but still unable to display the file.
js file::
import Controls = require("VSS/Controls");
import VSS_Service = require("VSS/Service");
import TFS_Build_Contracts = require("TFS/Build/Contracts");
import TFS_Build_Extension_Contracts = require("TFS/Build/ExtensionContracts");
import DT_Client = require("TFS/DistributedTask/TaskRestClient");
import { error } from "azure-pipelines-task-lib";
export class InfoTab extends Controls.BaseControl {
constructor() {
super();
}
public initialize(): void {
super.initialize();
// Get configuration that's shared between extension and the extension host
var sharedConfig: TFS_Build_Extension_Contracts.IBuildResultsViewExtensionConfig = VSS.getConfiguration();
var vsoContext = VSS.getWebContext();
if(sharedConfig) {
// register your extension with host through callback
sharedConfig.onBuildChanged((build: TFS_Build_Contracts.Build) => {
this._initBuildInfo(build);
var taskClient = DT_Client.getClient();
taskClient.getPlanAttachments(vsoContext.project.id, "build", build.orchestrationPlan.planId,"ATTACHMENT_TYPE_HERE").then((taskAttachments) => {
$.each(taskAttachments, (index, taskAttachment) => {
if (taskAttachment._links && taskAttachment._links.self && taskAttachment._links.self.href) {
var recId = taskAttachment.recordId;
var timelineId = taskAttachment.timelineId;
taskClient.getAttachmentContent(vsoContext.project.id, "build", build.orchestrationPlan.planId,timelineId,recId,"ATTACHMENT_TYPE_HERE",taskAttachment.name).then((attachementContent)=> {
function arrayBufferToString(buffer){
var arr = new Uint8Array(buffer);
var str = String.fromCharCode.apply(String, arr);
return str;
}
var data = arrayBufferToString(attachementContent);
});
}
});
});
});
}
}
private _initBuildInfo(build: TFS_Build_Contracts.Build) {
}
}
InfoTab.enhance(InfoTab, $(".build-info"), {});
// Notify the parent frame that the host has been loaded
VSS.notifyLoadSucceeded();
Html file:
<!DOCTYPE html>
<head>
<script src="../lib/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init( {
usePlatformScripts: true,
// moduleLoaderConfig: {
// paths: { "sample": "sample" }
// }
});
VSS.ready(function() {
require(["sample/tab2"], function () { });
});
</script>
<style>
.build-info {
padding: 10px;
}
</style>
</head>
<body>
<div class="build-info"> </div>
</body>
</html>
The issue is resolved, actually the issue was with my vss-extension.json file. I had to declare scope:
"scopes": [
"vso.build_execute"
]

Cannot get instance of CKEditor

I have several fields which need to be initialized with CKEditor, for this I have created an helper class that contains the initEditor method.
The method below should return the initialized editor but it doesn't:
window.CKEditorHelper = window.CKEditorHelper || {};
(function (exports) {
exports.initEditor = function (input, myEditor) {
ClassicEditor
.create(document.querySelector(input), {
language: {
ui: 'en'
content: 'en'
}
})
.then(editor => {
myEditor = editor;
});
};
})(window.CKEditorHelper);
this is called in the following way:
let editor = null;
CKEditorHelper.initEditor('#description', editor);
so when I click on a button:
$('#save').on('click', function(){
console.log(editor.getData());
});
I get:
Cannot read property 'getData' of null
what I did wrong?
There are some issues on your code
let editor = null;
the let keyword only define a variable within function scope, when you use editor on another scope (your click handle event), it could be undefined
Another line
myEditor = editor;
This just simple made the reference to your original editor object will gone
Here is my solution to fix it
Change the way you init an editor like bellow
window.editorInstance = {editor: null};
CKEditorHelper.initEditor('#description', editorInstance);
Change your CKEditorHelper to
window.CKEditorHelper = window.CKEditorHelper || {};
(function (exports) {
exports.initEditor = function (input, myEditorInstance) {
ClassicEditor
.create(document.querySelector(input), {
language: {
ui: 'en'
content: 'en'
}
})
.then(editor => {
myEditorInstance.editor = editor;
});
};
})(window.CKEditorHelper);
And when you want to use your editor
console.log(editorInstance.editor.getData());
You can give this in javascript
$(document).ready(function () {
CKEDITOR.replace('tmpcontent', { height: '100px' })
})
take the value by using following
$('#save').on('click', function(){
var textareaValue = CKEDITOR.instances.tmpcontent.getData();
});
<label class="control-label">Message</label>
<textarea name="tmpcontent" id="tmpcontent" class="form-control"></textarea>
//OR in latest version
var myEditor;
ClassicEditor
.create( document.querySelector( '#description' ) )
.then( editor => {
console.log( 'Editor was initialized', editor );
myEditor = editor;
} )
.catch( err => {
console.error( err.stack );
} );
and then get data using
myEditor.getData();

Override JavaScript class prototype inside a JavaScript class

I tried to override the Text class prototype function doSomething.
The class Editor initialises its txt with Text inside the _initText function.
The issue is my override code has never been really taken and overridden the original code.
No errors, when I run editor.txt.doSomething() it still print the "old message".
Please help on how to override this type of prototype in JavaScript. Plenty thanks.
editor.js
<!-- language: lang-js -->
function Text(editor) {
this.editor = editor;
}
Text.prototype = {
constructor: Text,
init: function init() {
this.doSomething();
},
doSomething: function doSomething() {
console.log('old message');
},
};
function Editor(editor) {
this.editor = editor;
}
Editor.prototype = {
constructor: Editor,
_initText: function _initText() {
this.txt = new Text(this);
this.txt.init();
},
init: function init() {
this._initText();
}
};
extension.js
<!-- language: lang-js -->
// When DOM is loaded
Text.prototype.doSomething = function() {
console.log('new message');
};
index.html
<!-- language: lang-html-->
// When DOM is loaded
editor = new Editor();
editor.init();

Aurelia Google SignIn Button

I just started using Aurelia and I am having a problem with Google Sign in. It looks like I might be able to create my own Google button but I'd rather get it to work this way if it is possible. Here is my code:
<script src="https://apis.google.com/js/platform.js" async defer></script>
...
<body aurelia-app="src/main">
...
<span id="googleButtonPlaceholder" class="g-signin2" data-onsuccess="onSignIn"></span>
I have the function setup in my Aurelia class but I do not know if/how I can call it. I have tried ${onSignIn()} which just calls the function when it loads, ${onSignIn}, onSignIn(), onSignIn, data-onsuccess.bind="onSignin()" but nothing seems to work. Is there a way to pass the Aurelia function to the Google data-onsuccess attribute?
As a note, I am switching from Angular 1.5.8 where this previously worked.
Here's an example: https://gist.run?id=5da90f48b43b9c5867c8d2ace0f6371f
app.html
<template>
<require from="google-signin-button"></require>
<google-signin-button success.call="signinSuccess(googleUser)"
error.call="signinError(error)">
</google-signin-button>
<h1>${message}</h1>
</template>
app.js
export class App {
message = 'Not signed in.';
signinSuccess(googleUser) {
const name = googleUser.getBasicProfile().getName();
this.message = `Signed in: ${name}`;
}
signinError(error) {
this.message = `Error: ${error}`;
}
}
google-signin-button.js
import {inject, noView, bindable} from 'aurelia-framework';
const googleSigninClientID = '927519533400-mfupo3lq9cjd67fmmvtth7lg7d8l50q9.apps.googleusercontent.com';
function preparePlatform() {
// https://developers.google.com/identity/sign-in/web/build-button
// The name of the global function the platform API will call when
// it's ready.
const platformCallbackName = 'setGooglePlatformReady';
// An "API ready" promise that will be resolved when the platform API
// is ready.
const ready = new Promise(
resolve => window[platformCallbackName] = resolve);
// Inject the client id meta tag
const meta = document.createElement('meta');
meta.name = 'google-signin-client_id';
meta.content = googleSigninClientID;
document.head.appendChild(meta);
// Inject an async script element to load the google platform API.
// Notice the callback name is passed as an argument on the query string.
const script = document.createElement('script');
script.src = `https://apis.google.com/js/platform.js?onload=${platformCallbackName}`;
script.async = true;
script.defer = true;
document.head.appendChild(script);
return ready;
}
const platformReady = preparePlatform();
#noView()
#inject(Element)
export class GoogleSigninButton {
#bindable success = googleUser => { };
#bindable error = error => { };
#bindable scope = 'profile email';
#bindable theme = 'dark';
#bindable width = 240;
#bindable height = 50;
constructor(element) {
this.element = element;
}
attached() {
platformReady.then(this.renderButton);
}
renderButton = () => {
gapi.signin2.render(this.element, {
scope: this.scope,
width: this.width,
height: this.height,
longtitle: true,
theme: this.theme,
onsuccess: googleUser => {
console.info(googleUser);
this.success({ googleUser });
},
onfailure: error => {
console.error(error);
this.failure({ error });
}
});
}
}
#JeremyDanyow had a great answer but after I went to bed and read a little more about Aurelia, I thought of a solution to try before seeing his answer so I thought I'd share an alternate approach for those interested.
index.html
<main aurelia-app="src/main">
</main>
<script src="https://apis.google.com/js/platform.js" async defer></script>
app.html
<template>
<span id="my-signin2"></span>
<!-- other stuff -->
</template>
app.js
attached() {
this.render();
}
render() {
gapi.signin2.render('my-signin2', {
'scope': 'profile email',
'theme': 'dark',
'onsuccess': this.onSuccess,
'onfailure': this.onFailure
});
}
onSuccess(googleuser) {
let gUser = googleuser.getBasicProfile(),
id_token = googleuser.getAuthResponse().id_token;
}
onFailure(error) {
console.log(error);
}
This approach differs slightly from what Google shows on their website where they have you give platform.js an onload function to render the button. Instead, I create the button in the template and then once the template is done being loaded, attached() is called, which in turn, calls the function I would have had platform.js call onload.
Try data-onsuccess.call="onSignIn()".
After following #JeremyDanyow's example around a few corners I came up with this
It works ok for simple usage, but needs help...
when there is another window open using a google login there is an error loading something in the iframe google adds (this doesn't seem to break it)
the listeners don't work for more than a couple of login/logouts at most
Here's hoping that someone else can improve upon this.
google-signin-button.js
import { inject, noView, bindable } from 'aurelia-framework';
import { LogManager } from 'aurelia-framework';
const Console = LogManager.getLogger('google-signin-button');
// Integrating Google Sign-In into your web app
// https://developers.google.com/identity/sign-in/web/reference
// https://console.developers.google.com/apis/credentials
// inspiration: https://developers.google.com/identity/sign-in/web/build-button
function preparePlatform(): Promise<Function> {
// Inject an async script element to load the google platform API.
const script = document.createElement('script');
script.src = `https://apis.google.com/js/platform.js?onload=gapi_ready`;
script.async = true;
script.defer = true;
document.head.appendChild(script);
// return a promise that will resolve with the onload callback
return new Promise(resolve => window['gapi_ready'] = resolve);
}
#noView
#inject(Element)
export class GoogleSigninButton {
#bindable authenticated = (signedIn: Boolean) => { };
#bindable authorized = (GoogleUser: any) => { };
#bindable scope = 'profile email';
#bindable clientId = 'none';
#bindable theme = 'dark';
#bindable width = 240;
#bindable height = 50;
public element: Element;
constructor(element) {
this.element = element;
}
public wasAuthenticated: Boolean;
sendAuthenticated(signedIn: Boolean) {
if (signedIn !== this.wasAuthenticated) {
this.authenticated(signedIn);
this.wasAuthenticated = signedIn;
}
}
public wasAuthorized: any;
sendAuthorized(googleUser: any) {
if (googleUser !== this.wasAuthorized) {
this.authorized(googleUser);
this.wasAuthorized = googleUser;
this.sendAuthenticated(true);
}
}
attached() {
// inject the script tag
preparePlatform()
.then(() => {
// load the auth lib
// Console.debug('gapi created, loading auth2');
window['gapi'].load('auth2', () => {
// init the auth lib
// Console.debug('gapi.auth2 loaded, intializing with clientId:', this.clientId);
window['gapi'].auth2.init({
client_id: this.clientId
})
.then(
(googleAuth: any) => {
// Console.debug('gapi.auth2 intialized');
// listen for user signed in/out
googleAuth.isSignedIn.listen((signedIn: Boolean) => {
// Console.debug('googleAuth.isSignedIn.listener', signedIn);
this.sendAuthenticated(signedIn);
});
// listen for who signed in
googleAuth.currentUser.listen((googleUser: any) => {
// Console.debug('googleAuth.currentUser.listener', googleUser);
this.sendAuthorized(googleUser);
});
// draw the button
window['gapi'].signin2.render(this.element, {
scope: this.scope,
width: this.width,
height: this.height,
longtitle: true,
theme: this.theme,
onsuccess: (googleUser: any) => {
// Console.debug('gapi.signin2.render success', googleUser);
this.sendAuthorized(googleUser);
},
// drawing button failure
onfailure: (error: any) => {
Console.error('gapi.signin2.render failure', error);
}
});
},
// intialization error
(errObj: any) => {
Console.error('gapi.auth2.init -> errObj', errObj);
}
);
});
});
}
}
some-usage.js
import environment from '../environment';
import { LogManager } from 'aurelia-framework';
const Console = LogManager.getLogger('Login');
import { inject } from 'aurelia-framework';
import { AuthService } from 'aurelia-authentication';
import { EventAggregator } from 'aurelia-event-aggregator';
import './login.scss';
#inject(AuthService, EventAggregator)
export class Login {
public authService: AuthService;
public eventAggregator: EventAggregator;
public googleSigninClientID: string = 'none';
constructor(authService: AuthService, eventAggregator: EventAggregator) {
this.eventAggregator = eventAggregator;
this.authService = authService;
this.googleSigninClientID = environment.googleSigninClientID;
};
isAuthenticated(signedIn: Boolean) {
Console.warn('isAuthenticated', signedIn);
}
isAuthorized(googleUser: any) {
Console.warn('isAuthorized', googleUser);
}
}
some-usage.html
<template>
<require from="../resources/elements/google-signin-button"></require>
<section>
<div class="container-fluid">
<center>
<google-signin-button client-id.bind="googleSigninClientID" authenticated.bind="isAuthenticated" authorized.bind="isAuthorized"> </google-signin-button>
</center>
</div>
</section>
</template>

Categories

Resources