Component second time render hides the input focus of first component in stencilJS - stenciljs

I am trying to learn stencilJS, I am developing the component which has input field with auto focus.
I am calling component twice in index.html, I am getting into strange issue the second render component is taking autofocus of first render component.
And the components are not render on same order every time, the component which renders last it's input field will have the focus remaining will not get autofocus.
Attached code below, please help me to sort out.
index.html
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
<title>Stencil Component Starter</title>
<script type="module" src="/build/testing.esm.js"></script>
<script nomodule src="/build/testing.js"></script>
</head>
<body>
<search-input data-test-id="test-two" place-holder="Search" value=""></search-input>
<search-input data-test-id="test-three" place-holder="Search" value=""></search-input>
</body>
</html>
Search-plugin.tsx
import { Component, h, Prop, Event, EventEmitter } from '#stencil/core';
#Component({
tag: 'search-input',
styleUrl: 'search-input.css',
shadow: true,
})
export class SearchInput {
private searchInput: HTMLInputElement;
#Prop() value = "";
#Prop() dataTestId!: string;
#Prop() placeHolder: string = "Search";
#Event() clearSearch: EventEmitter;
#Event() searchRequest: EventEmitter;
protected componentDidLoad() {
this.searchInput.focus();
}
render() {
return (
<div data-test-id={this.dataTestId} class="items-center flex bg-white border-solid rounded-sm border-gray-300 box-border text-xl border-1 pl-15 pr-12 py-9">
<input
type="text"
ref={el => (this.searchInput = el as HTMLInputElement)}
data-test-id={`${this.dataTestId}-search-input`}
class="focus:outline-none border-0 flex-grow w-full pl-15"
value={this.value}
placeholder={this.placeHolder}
/>
</div>
);
}
}

Two elements - custom elements / web components or standard HTML elements - cannot have focus at the same time. But your code tries to do this - every search-input on the page will try to take focus when the Stencil lifecycle executes componentDidLoad - so that last one rendered by Stencil wins. The order of which is not predictable because Stencil is asynchronous.
If you only want one of the elements to have initial focus, add a property to the component to enable this. For example:
export class SearchInput {
...
#Prop() autoFocus: boolean = false;
...
componentDidLoad() {
if (this.autoFocus) {
this.searchInput.focus();
}
}
}
<search-input auto-focus data-test-id="test-two" place-holder="Search" value=""></search-input>
<search-input data-test-id="test-three" place-holder="Search" value=""></search-input>

Related

html2canvas not capturing images inside a react native webview

I want to capture the elements inside a WebView in react native.
This is the html of the WebView
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1" />
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossOrigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas#1.4.1/dist/html2canvas.min.js" integrity="sha256-6H5VB5QyLldKH9oMFUmjxw2uWpPZETQXpCkBaDjquMs=" crossOrigin="anonymous"></script>
</head>
<body>
<div class="certificateDOM">
<div class="container-fluid">
<div class="row d-flex justify-content-center" id="certificateBlock">
<img
class="imageTemplate"
src="file:///android_asset/images/certificate1.png"
alt="certificate Image" />
<span class="certificateId"></span>
<span class="certificateName">is here by awarded the certification of achievement for the successfull completion of <b class='certificateBoldName'></b></span>
<span class="userName"></span>
<img class="certificateSign" src="file:///android_asset/images/authority1.png" alt="authority sign" />
</div>
</div>
</div>
</body>
</html>
This will look like this :
The student name and authority signature are absolutely positioned on the image (I have not included styles for these).
The image src is a 'file:///' url due to how android references the android assets.
This is the function which uses html2canvas to convert the element to a canvas:
const takeCertificateSnap = ()=>{
window.scrollTo(0, 0);
return html2canvas(document.querySelector('#certificateBlock'), {
allowTaint:true,
useCORS: true,
});
};
const getCertificateImageDataUrl = ()=>{
const canvasPromise = takeCertificateSnap();
canvasPromise.then((canvas) => {
const dataUrl = canvas.toDataURL();
const obj={
action:'getCertificateImageDataUrl',
data: {
dataUrl,
}
};
window.ReactNativeWebView.postMessage(JSON.stringify(obj));
}).catch((e) => {
const errmsg={
action:'error',
data: {
cause: 'html2canvas',
error: err.message,
}
};
window.ReactNativeWebView.postMessage(JSON.stringify(errmsg));
});
}
the image I am getting as a result of capturing the element as canvas and converting it to a dataUrl is this
Although the image is of same-origin, the image is not being rendered.
I saw couple of posts and included allowTaint and useCORS options, but it did not help.
Any help is appriciated.

How to set focus for InputRadio / InputRadioGroup in Blazor?

I want to set the focus on the InputRadioGroup but it appears it doesn't have the ElementReference attribute unlike the other Blazor built-in form components. Should I just extend the InputRadioGroup and add the ElementReference or is there another way to set focus on the InputRadio or InputRadioGroup?
You could refer to the sample below to focus on the InputRadio.
Vehicle.cs
namespace BlazorApp1.Model
{
public class Vehicle
{
public string Name { get; set; }
}
}
file1.js
window.jsfunction = { focusElement: function (id) { const element = document.getElementById(id); element.focus(); } }
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>BlazorApp1</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="BlazorApp1.styles.css" rel="stylesheet" />
<script src="file1.js"></script>
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
Reload
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
Index.razor
#inject IJSRuntime js
#page "/"
<div>
<h4> vehicle Selected - #vehicle.Name </h4>
<EditForm Model="vehicle">
<InputRadioGroup #bind-Value="vehicle.Name" >
#foreach (var option in rdOptions)
{
<InputRadio Value="option" id=#option #onfocus="alrt" /> #option <br />
}
</InputRadioGroup>
<br>
<input Id="idPassWord" Type="password" />
<button #onclick="clickOK">Set Focus</button>
</EditForm>
</div>
#code{
BlazorApp1.Model.Vehicle vehicle=new BlazorApp1.Model.Vehicle(){Name = "auto"};
List<string> rdOptions = new List<string> { "car", "bus", "auto" };
private async void clickOK()
{
await Focus("car");
}
private void alrt()
{
Console.WriteLine("Element focused");
}
public async Task Focus(string elementId)
{
await js.InvokeVoidAsync("jsfunction.focusElement", elementId);
}
}
Output:
In the above code example, I am generating the InputRadio on the page which has the OnFocus event. While we try to set the Focus on the InputRadio using the JS code. OnFocus event gets fired and displays the message in a browser console. This proves that InputRadio is getting focused.
Further, you could modify the code as per your own requirements.
After some investigation, seems like the ability to focus for InputRadio/InputRadioGroup was removed due to some prior issues. They now returned the focus after I raised the issue, and it will be included to .NET 7.

StencilJS slot content flickering visible content

I am creating a simple StencilJS web component, I am passing details as slot to component. The content will be invisible till manually make visible by clicking the button.
But while the application loads slightly flickering is happening in the component.
How to avoid flickering, but my requirement is to pass the details as slot.
Component.tsx
import { Component, Prop, h, State } from '#stencil/core';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
detailBox: HTMLElement;
showDetails = () => {
this.detailBox.classList.remove('display-none');
};
render() {
return (
<div>
<button onClick={this.showDetails}>Show detail</button>
<span
id="detail"
ref={elem => {
this.detailBox = elem;
}}
class="display-none"
>
<slot></slot>
</span>
</div>
);
}
}
component.css
:host {
display: block;
}
.display-none {
visibility: hidden;
}
index.html
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
<title>Stencil Component Starter</title>
<script type="module" src="/build/display-flickering.esm.js"></script>
<script nomodule src="/build/display-flickering.js"></script>
<style data-styles></style>
</head>
<body>
<my-component> <button>I am from slot</button></my-component>
</body>
</html>

VueJS: Call function from bootstrap/jQuery script includes in template/layout from a component

i am very new to VueJS and want to build an Admin Dashboard for an existing bootstrap template (SB Admin Pro). I know there is a BootstrapVUE but we want to use the specified template that we purchased before. So this is not an option for me/us.
My Goal:
In our vue component we make an axios call to our backend to retrieve and show some data. If the call fails we want to call in the catch block a bootstrap function for toast to show some notification to the user (like: Error while fetching data from backend...). We included the bootstrap and jquery libraries from the template in the default index.html.
The Problem:
I don't know how to call the toasts (or other) functions from the vue component. In the template the call looks like this:
$("#toastBasic").toast("show");
Our index.html looks
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="svote UG (haftungsbeschränkt)" />
<script data-search-pseudo-elements defer src="js/font-awesome-5.11.2.min.js" crossorigin="anonymous"></script>
<script src="./js/feather.min.js" crossorigin="anonymous"></script>
</head>
<body class="nav-fixed">
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
</div>
<!-- built files will be auto injected -->
<script defer src="js/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
<script defer src="js/bootstrap.min.js" crossorigin="anonymous"></script>
<script defer src="js/script.js"></script>
</body>
</html>
Our vue component:
<template>
<main>
<div style="position: absolute; top: 1rem; right: 1rem;">
<!-- Toast container -->
<div style="position: absolute; bottom: 1rem; right: 1rem;">
<!-- Toast -->
<div class="toast" id="toastBasic" role="alert" aria-live="assertive" aria-atomic="true" data-delay="5000">
<div class="toast-header">
<i data-feather="bell"></i>
<strong class="mr-auto">Toast with Autohide</strong>
<small class="text-muted ml-2">just now</small>
<button class="ml-2 mb-1 close" type="button" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body">This is an example toast alert, it will dismiss automatically, or you can dismiss it manually.</div>
</div>
</div>
</div>
<ContentHeader title="Blank" icon="fas fa-file" subtitle="A blank page to get you started!" />
<div class="container-fluid mt-n10">
<div v-if="error" class="alert alert-danger" role="alert">
{{ error }}
</div>
<div class="row">
<Card cHeader="Eine Karte" class="col-md-12"> {{ contacts }}</Card>
</div>
</div>
</main>
</template>
<script>
import ContentHeader from '../../components/ContentHeader'
import Card from '../../components/Card'
import axios from 'axios';
export default {
name: "Contact",
components: {
ContentHeader,
Card,
},
data() {
return {
contacts: null,
error: null
}
},
mounted() {
const url = 'http://localhost:3000/v1/';
axios.get(url + 'contsact')
.then(response => {
this.contacts = response.data
console.log(response)}
)
.catch(error => {
console.log(error.response)
$("#toastBasic").toast("show");
});
}
}
</script>
In vue.config.js, specify jquery as external (this tells webpack where to provide jquery from when it's imported in any component):
configureWebpack: {
externals: {
jquery: 'window.jQuery'
}
}
Place all the <script>s you want loaded by the time Vue inits in your public/index.html page, in the <head> tag and remove their defer attribute. This includes any jquery plugin (or anything requiring/extending jquery) you might want to use in your Vue app (in your case, bootstrap.min.js).
The above will make it work when developing (in serve). You'll need to do the same for prod: Load jquery and any dependency before initing the Vue app.
Now you can safely use
import * as $ from 'jquery'
in any component.
Webpack will place in $ whatever window.jQuery is at the moment the component inits.
The above approach makes sure all required scripts are loaded before Vue inits (which is a bit extreme, but it makes sure there's no way you can call the jquery method before its dependencies are loaded).
If you don't want to wait for jquery and bootstrap.min.js to load before you init your Vue app, a trick you could use is to assign jquery from window object just before you need it:
yourAlertMethod() {
const $ = window.jQuery;
$.toast()...
}
Obviously, you no longer have to move all the scripts in <head> and to remove their defer. This second method doesn't guarantee they would have already loaded before your method is first used. But your app inits faster.
Here's a basic example.
I used the second method, codesandbox.io doesn't have support for #vue/cli v3 hence vue.config.js doesn't work as in a Vue project created with vue create. Therefore, I had to use the second method.
The full list of dependecies you need to load before you call the $(el).toast() method:
bootstrap.min.css
jquery.js
popper.js
bootstrap.min.js
(see them in public/index.html). You can copy/paste them from Bootstrap.
You can get ref of the element and pass it to jQuery
<div ref="toast" class="toast" id="toastBasic" role="alert" aria-live="assertive" aria-atomic="true" data-delay="5000">
import $ from 'jQuery';
$(this.$refs.toast).toast("show");
I don't recommend that though.

How to structure nested reactive data in VUE

I think this is a fairly common problem.
You fetch a list from an api, and then you display that list via
<div v-for="item in items">
<checkbox v-model="item.checked">
</div>
Now My question is about the checked property. when iterating over a list of undefined length, of unknown keys, it seems the checked property has to be created and attached to the item object like so
computed: {
items () {
var newList = Object.assign([], this.rootList) // shallow clone the api list
for (var i of newList) {
i.checked = false
// or
Vue.set(i, 'checked', false)
}
return newList
}
However this is not making my checkbox reactive. But more importantly, this way of adding new properties to the rootList object clone, is this the best practice? and if so, why is this not reactive? Even when using Vue.set
Computed properties are by default getter-only [...]
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
This might help: https://jsfiddle.net/eywraw8t/187063/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div v-for="item in items">
<input type="checkbox" v-model="item.checked"> {{ item.name }}
</div>
<button #click="fetch">Fetch more items</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
items: []
},
methods: {
fetch() {
let itemsFromApiResponse = [
{ name: "Test 1" },
{ name: "Test 2" },
{ name: "Test 3" },
];
itemsFromApiResponse.forEach(item => this.items.push(Object.assign({ checked: false }, item)));
}
}
})
</script>
</body>
</html>