webcomponent in template and alpine.js - web-component

I would like to define a custom element and attach it in a shadow dom. This element use alpine and bootstrap4 and must be graphically isolated from the rest of the dom. Alpine must be included and used directly inside the webcomponent (not in the light dom).
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />
<script defer src="//unpkg.com/alpinejs#3.9.3/dist/cdn.min.js"></script>
<div class="container" x-data="my_component">
<div class="row">
<div class="col-lg-2 col-md-4">
<div class="form-group">
<label for="sel1">Sel 1</label>
<select id="sel1" class="form-control" x-model="sel1">
<option value="" disabled>Sel 1</option>
<template x-for="{label, id} in optsSel1" :key="id">
<option
x-text="label"
x-bind:value="id"
x-bind:selected="id === sel1"
></option>
</template>
</select>
</div>
</div>
</div>
<div class="row mt-xs-3 mt-sm-3">
<div class="col-lg-12 text-center">
<button class="btn btn-warning btn-lg btn-block" #click="notify">
Submit
</button>
</div>
</div>
</div>
<script>
const optsSel1 = [{
id: "AF",
label: "Afghanistan"
},
{
id: "AL",
label: "Albania"
},
{
id: "DZ",
label: "Algeria"
}
];
document.addEventListener("alpine:init", () => {
Alpine.data("my_component", () => ({
optsSel1,
sel1: "AF",
notify() {
const data = new URLSearchParams({
sel1: this.sel1
});
alert(data);
}
}));
});
</script>
I tried to wrap the code above in a template tag and define a custom element.
Graphically is ok, bootstrap seems to work.
JS works too indeed on alpine:init an alert is shown.
But all the logic defined in alpine that interact with the dom seems to not working:
select's options are empty;
click on submit button has no effect.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<button class="btn btn-warning btn-lg btn-block">button outside</button>
<my-component />
</div>
<template id="my-component_tpl">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap#4.4.1/dist/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<script defer src="//unpkg.com/alpinejs#3.9.3/dist/cdn.min.js"></script>
<div class="container" x-data="my_component">
<div class="row">
<div class="col-lg-2 col-md-4">
<div class="form-group">
<label for="sel1">Sel 1</label>
<select id="sel1" class="form-control" x-model="sel1">
<option value="" disabled>Sel 1</option>
<template x-for="{label, id} in optsSel1" :key="id">
<option
x-text="label"
x-bind:value="id"
x-bind:selected="id === sel1"
></option>
</template>
</select>
</div>
</div>
</div>
<div class="row mt-xs-3 mt-sm-3">
<div class="col-lg-12 text-center">
<button class="btn btn-warning btn-lg btn-block" #click="notify">
Submit
</button>
</div>
</div>
</div>
<script>
const optsSel1 = [{
id: "AF",
label: "Afghanistan"
},
{
id: "AL",
label: "Albania"
},
{
id: "DZ",
label: "Algeria"
}
];
document.addEventListener("alpine:init", () => {
alert("alpine:init " + JSON.stringify(optsSel1, null, 2));
Alpine.data("my_component", () => ({
optsSel1,
sel1: "AF",
notify() {
const data = new URLSearchParams({
sel1: this.sel1
});
alert(data);
}
}));
});
</script>
</template>
<script>
customElements.define(
"my-component",
class extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({
mode: "open"
});
const tpl = document.getElementById("my-component_tpl");
shadow.appendChild(tpl.content);
}
}
);
</script>
</body>
</html>
What'is wrong? Thanks in advance!

You should add
document.addEventListener("alpine:initialized",()=>{
Alpine.initTree(this.shadowRoot)
})
to the connectedCallback function.
Alpine's mutation observer can't see what's inside a shadow dom, so you have to manually initialize it.
See your snippet fixed

Related

How can I make button minus and plus on the bootstrap vue?

I get tutorial from here : https://bootstrap-vue.js.org/docs/components/form-input
I want to make like this :
I try like this :
<template>
...
<b-col class="pr-0 custom-control-inline">
<b-btn variant="warning btn-sm mr-1 mt-1 mb-1"><i class="fa fa-minus-circle"></i></b-btn>
<b-form-input type="number" class="col-md-3" v-model="quantity"></b-form-input>
<b-btn variant="info btn-sm ml-1 mt-1 mb-1"><i class="fa fa-plus-circle"></i></b-btn>
</b-col>
...
</template>
<script>
export default {
data () {
return {
quantity: null
}
}
}
</script>
The result like this :
How can I make button plus and minus works?
So if click button plus, the quantity go up
From the docs:
<b-input-group>
<b-input-group-prepend>
<b-btn variant="outline-info">-</b-btn>
</b-input-group-prepend>
<b-form-input type="number" min="0.00"></b-form-input>
<b-input-group-append>
<b-btn variant="outline-secondary">+</b-btn>
</b-input-group-append>
</b-input-group>
Just style the buttons as you like and add click event to the buttons for the input number logic, like:
new Vue({
el: '#app',
data: {
num: 0
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css" />
<!-- Add this after vue.js -->
<script src="https://unpkg.com/vue"></script>
<script src="//unpkg.com/babel-polyfill#latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-input-group>
<b-input-group-prepend>
<b-btn variant="outline-info" #click="num--">-</b-btn>
</b-input-group-prepend>
<b-form-input type="number" min="0.00" v-model="num"></b-form-input>
<b-input-group-append>
<b-btn variant="outline-secondary" #click="num++">+</b-btn>
</b-input-group-append>
</b-input-group>
</div>
Maybe like this:
<template>
<div class="quantity">
<b-input-group>
<b-input-group-prepend>
<b-btn variant="info" #click="decrement()">-</b-btn>
</b-input-group-prepend>
<b-form-input type="number" min="0.00" :value="quantity"></b-form-input>
<b-input-group-append>
<b-btn variant="info" #click="increment()">+</b-btn>
</b-input-group-append>
</b-input-group>
</div>
</template>
You can use type="number" or type="text".
And simple logic:
<script>
export default {
name: "Quantity",
data() {
return {
quantity: 1
};
},
methods: {
increment() {
this.quantity++;
},
decrement() {
if (this.quantity === 1) {
alert("Negative quantity not allowed");
} else {
this.quantity--;
}
}
}
};
</script>
Codesandbox example

Bootstrap issue when using select 2

I'm using select2 for typeahead. The functionaity works fine but it distorts my input fields.
For example this code
<div class="container">
<div class="card">
<div class="card-header">Search for a Property</div>
<div class="card-body">
<div class="row">
<!-- County Column -->
<div class="col-md-3">
<select class ="form-control" id="county" name="county">
<!--Gets all counties from DB -->
<option> - Select a County - </option>
#foreach ($county as $counties)
<option>{{$counties->name}}</option>
#endforeach
</select>
</div>
<div class="col-md-3">
<select class="town form-control" name="town">
</select>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$('.town').select2({
placeholder: 'Select an item',
ajax: {
url: '/townsearch',
dataType: 'json',
delay: 250,
processResults: function (data) {
return {
results: $.map(data, function (item) {
return {
text: item.name,
id: item.name
}})};},
cache: true
}});
</script>
Will produce the following
Image of two dropdowns.
Dropdown 1 is the way I want it. Dropdown 2 is the effect of select2. Any fixes?

Displaying Error Message in Bootstrap Modal in Spring MVC Project

Is it possible to display "User with this username does not exist" Error Message in Bootstrap Modal in Spring MVC Project through the controller?
login.jsp
<div class="modal-body">
<form id="register-form" role="form" autocomplete="off" class="form"
method="POST"
action="${pageContext.request.contextPath }/recoverPassword">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon"><i
class="glyphicon glyphicon-envelope color-blue"></i></span> <input
id="email" name="username" placeholder="your username"
class="form-control" type="text"> ${error}
</div>
</div>
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">
Send email link</button>
</form>
controller
#RequestMapping(value = "/recoverPassword", method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.NO_CONTENT)
public void sendingEmailForForgot(#RequestParam("username") String username) {
// Check whether email exists in DB
System.out.println(userDAO.getUserByName(username));
System.out.println(username);
if (userDAO.getUserByName(username) == null) {
System.out.println("This ID does not exists");
redirectAttributes.addAttribute("error", "wrong");
} else {
System.out.println("mail sent to your id");
}
// If exists then hash and send url link
// else error
}
I have validated username whether if exists, but how to display that error on same currently opened modal?
If not possible through controller please share any other way to do so.
Finally solved it through AJAX, still don't know if possible through the controller. Works fine for me, I have used this:
function doAjaxPost() {
// get the form values
var name = $('#name').val();
var education = $('#education').val();
$.ajax({
type: "POST",
url: contexPath + "/AddUser.htm",
data: "name=" + name + "&education=" + education,
success: function(response){
// we have the response
if(response.status == "SUCCESS"){
userInfo = "<ol>";
for(i =0 ; i < response.result.length ; i++){
userInfo += "<br><li><b>Name</b> : " + response.result[i].name +
";<b> Education</b> : " + response.result[i].education;
}
userInfo += "</ol>";
$('#info').html("User has been added to the list successfully. " + userInfo);
$('#name').val('');
$('#education').val('');
$('#error').hide('slow');
$('#info').show('slow');
}else{
errorInfo = "";
for(i =0 ; i < response.result.length ; i++){
errorInfo += "<br>" + (i + 1) +". " + response.result[i].code;
}
$('#error').html("Please correct following errors: " + errorInfo);
$('#info').hide('slow');
$('#error').show('slow');
}
},
error: function(e){
alert('Error: ' + e);
}
});
}
Following trick worked for me
1. put ${error} in p tag with unique id
2. On login.jsp page load,check if innerHtml of p tag is empty.
3 If it is not empty open the login modal.
function modelopen(){
var temp = document.getElementById("errormsg").innerHTML;
if(temp!==""){
$('#modallogin').modal('show');
}
You can try:
1) RestController instead of Controller (I think that this is way you solved this)
2) You can use validations and return your error as part of BindingResult https://docs.spring.io/spring/docs/4.1.x/spring-framework-reference/html/validation.html
3) Use exceptionHandler
function errorshow() {
const input1 = document.getElementById('input1').value;
const input2 = document.getElementById('input2').value;
const div = document.getElementById("erroM");
if (input1 == '' && input2 == '') {
div.innerHTML = "hey";
setTimeout(() => {
div.remove()}, 2000);
}}
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
<title>Erorr Show in Modal</title>
</head>
<body>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form>
<div class="modal-body">
<div id="erroM"></div>
<div>
<input id="input1" type="text">
<input id="input2" type="text">
<input id="input3" type="text">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="errorshow()">save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Button trigger modal -->
<button id="button" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">Click</button>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
<script src="main.js"></script>
</body>
</html>

ng-view expression is; not working

index.cshtml
<head>
<meta name="viewport" content="width=device-width" />
<title></title>
<script src="~/Scripts/angular.min.js"></script>
<script src="~/Scripts/angular-route.min.js"></script>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<script src="~/App/App.js"></script>
<script src="~/App/Controllers/FlightCtrl.js"></script>
</head>
<body>
<div id="wrapper">
Hi
#*<resolve-loader></resolve-loader>*#
<div class="nav navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav" >
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li class="dropdown">
<a>Add Flight</a>
</li>
</ul>
</div>
</div>
</div>
<div ng-view> </div>
</body>
</html>
App.js
var AirReservationApp = angular.module("AirReservationApp", ['ngRoute']);
AirReservationApp.config(['$routeProvider',
function ($routeProvider)
{
$routeProvider.
when('/Admin', { templateUrl: 'App/Views/Flights/AddFlight.htm', controller: 'FlightCtrl' }).
otherwise({
redirectTo: '/Admin'
});
}
]);
AddFlight.html
<div ng-controller="FlightCtrl">
<ng-form name="formFlight">
<div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<label class="col-xs-12 col-sm-12 col-md-12 col-lg-12" for="question">Question <span class="mandatory">*</span> </label>
<div class="form-group col-xs-9 col-sm-9 col-md-8 col-lg-8"
<input type="text" name="FlightCode"
ng-required="=true" existnamevalidate valid="YN"
placeholder="Enter Flight Code"
ng-model="CreateFlight.Flight.FlightCode">
<span class="help-block"
Flight code is required.>
</span>
<span class="help-block"
This Flight Code already exists.>
</span>
</div>
</div>
<!--ng-show="isTextType"-->
<div class="row btn-pos-change">
<div class="col-md-12 padding-top-12 btn-blocks">
<div class="form-group">
<button type="button" class="btn btn-success pull-right"
>
{{saveBtnText}}
</button>
</div>
</div>
</div>
</div>
</ng-form>
</div>
FlightCtrl.js
var FlightCtrl = angular.module("FlightCtrlModule", []);
FlightCtrl.controller("FlightCtrl", ["$scope", "$rootScope", "$timeout", "$window", "$route",
function ($scope, $rootScope, $timeout, $window, $route)
{
alert('Hello');
$scope.saveBtnText = "Create";
}]);
The expression {{saveBtnText}} does not show 'Create'. Can any one help me on this.
Try this.
First create the FlightCtrlModule module on top of the AirReservationApp module in the App.js and the inject it to the AirReservationApp module.
App.js
angular.module("FlightCtrlModule", []);
var AirReservationApp = angular.module("AirReservationApp", ['ngRoute', 'FlightCtrlModule']);
AirReservationApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/Admin', { templateUrl: '/home.html', controller: 'FlightCtrl' }).
otherwise({
redirectTo: '/Admin'
});
}
]);
Now attach the FlightCtrl controller to the AirReservationApp module to have access to the FlightCtrlModule module.
FlightCtrl
//var FlightCtrl = angular.module("FlightCtrlModule", []);
AirReservationApp.controller("FlightCtrl", ["$scope", "$rootScope", "$timeout", "$window", "$route",
function ($scope, $rootScope, $timeout, $window, $route) {
alert('Hello');
$scope.saveBtnText = "Create";
}]);
UPDATE
If you want to keep your approach, what you can do is, move the flight controller above the app.js like so:
<script src="~/App/Controllers/FlightCtrl.js"></script>
<script src="~/App/App.js"></script>
And then inject the module FlightCtrlModule to the AirReservationApp module, like so:
var AirReservationApp = angular.module("AirReservationApp", ['ngRoute', 'FlightCtrlModule']);
both way have the same result.
Example Project - Dropbox

How to position ngshow messages in angular js

How to have ng-show messages beside the text box rather than underneath it. Right now it is displaying all the validation messages underneath the input box.
<!DOCTYPE html>
<html ng-app="myApp" >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script>
"use strict";
var app = angular.module('myApp', []);
app.controller('MainCtrl', function ($scope) {
$scope.cityArray = ["hyderabad", "secunderabad", "delhi", "mumbai"];
$scope.submit = function ($event) {
if ($scope.myForm.$invalid) {
// Or some other update
$scope.myForm.$submitted = true;
$event.preventDefault();
}
};
});
app.directive('uniqueUsername', function ($http) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
element.bind('blur', function (e) {
ngModel.$setValidity('unique', true);
$http.post('CheckUserName2.do').success(function (data) {
if (data) {
ngModel.$setValidity('unique', false);
}
});
});
}
};
});
</script>
</head>
<body ng-controller="MainCtrl">
<div class="container">
<div class="col-md-6 col-md-offset-0">
<div class="panel panel-login">
<div class="panel-body">
<div class="row">
<div class="col-lg-12">
<h2 class="text-muted">Registration form</h2>
<div>
<form name="myForm" action="RegistrationServlet.do" method="POST" >
First name:<input type="text" class="form-control input-sm" name="uname" ng-pattern="/^[a-zA-Z]{3,20}/" ng-model="uname" unique-username="" placeholder="First Name" required/>
<span style="color:red" ng-show="myForm.uname.$error.pattern">First name cannot be less than 3 letters with no digits</span>
<span style="color:red" class="error" ng-if="myForm.$submitted && myForm.uname.$error.required">Please fill field above<br></span><br/>
<span style="color:red" class="hide-while-in-focus" ng-show="myForm.uname.$error.unique">Username already exist<br/></span>
<button class="form-control btn btn-success" type="submit" ng-click="submit($event)">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Here's the plunker
You don't have space to the right to display error messages beside the input box. Putting error messages just above the submit button will cause a minor UI issue: with each error, your form submit-button will flicker vertically.
Viz.
- Submit your form without entering any input to get the error message.
- type/erase any character in the input box to see the flickering
Workaround:
Either keep the error messages below submit button or provide dedicated height to error messages to avoid undesirable effects or both
<form name="myForm" action="RegistrationServlet.do" method="POST" >
First name:<input type="text" class="form-control input-sm" name="uname" ng-pattern="/^[a-zA-Z]{3,20}/" ng-model="uname" unique-username="" placeholder="First Name" required/><br><br>
<button class="form-control btn btn-success" type="submit" ng-click="submit($event)">Submit</button>
<div style="height=100px">
<span style="color:red" ng-show="myForm.uname.$error.pattern">First name cannot be less than 3 letters with no digits</span>
<span style="color:red" class="error" ng-if="myForm.$submitted && myForm.uname.$error.required">Please fill field above<br></span><br/>
<span style="color:red" class="hide-while-in-focus" ng-show="myForm.uname.$error.unique">Username already exist<br/></span>
</div>
</form>
Demo

Resources