i am using angular's datepicker to create a booking service.
I am experiencing a huge problem, when I make the reservation I activate a dialog that confirms the outcome of the reservation, if the outcome is positive another dialog opens with the confirmation. when you click on the button 'ok', to close the final dialogue and return to the datepicker, a call starts that takes all the customer's reservations and colors the dates blue.
here the chaos happens:
the dateClass () method inside the appcomponent.ts file is activated several times and cycles the old reservation list and not the new one.
I attached the code:
APPCOMPONENT.HTML
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Clicca sul calendario</mat-label>
<input matInput readonly [matDatepicker]="picker" [(ngModel)]="this.shareDate.myFormattedDate" (dateChange)="openDialog($event)">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker [dateClass]="dateClass()" #picker> </mat-datepicker>
</mat-form-field>
APPCOMPONENT.TS
dateClass() {
return (date: Date): MatCalendarCellCssClasses => {
const unvailableArray = this.shareDate.unavailableDates;
const reservedArray = this.shareDate.reservedDate;
let day = 'freeDate';
for (const element of reservedArray) {
if (date.getFullYear() === element.getFullYear() && date.getMonth() === element.getMonth() &&
date.getDate() === element.getDate()) {
day = 'prenotation';
return day;
}
}
for (const element of unvailableArray) {
if (date.getFullYear() === element.getFullYear() && date.getMonth() === element.getMonth() &&
date.getDate() === element.getDate()) {
day = 'unavailable';
return day;
}
}
return day;
};
}
dialogConfirm.ts
click() {
const devCode = this.getQueryCode.getQueryParameter('d');
this.shareDate.device.deviceId = devCode;
this.postservices.getPrenotationList(this.shareDate.device).subscribe((resp1: GetDataResponse) => {
if (resp1) {
this.shareDate.foundDate = resp1;
for (const element of this.shareDate.foundDate.reservationdDates) {
this.shareDate.reservedDate.push(new Date(element));
}
}
console.log('resp1', resp1);
});
this.postservices.getPrenotationList(this.shareDate.device).subscribe((resp1: GetDataResponse) => {
if (resp1) {
this.shareDate.foundDate = resp1;
for (const element of this.shareDate.foundDate.unavailableDates) {
this.shareDate.unavailableDates.push(new Date(element));
}
}
});
}
I put console.log (), in the dateclass () and in click () when dateclass () starts, the log shows me that I have 3 reservations, when I make a reservation the click () log tells me that I have 4 reservations. but when I close the dialogue, dateclass () activates again and returns me to 3 reservations. if I close and restart the app it shows me 4 reservations ... what happens? what do I have to do?
Don't call function in template.Since dataClass input property expects MatCalendarCellClassFunction,assign function to dateClass property something like this.
component.ts
dateClass = (date: Date): MatCalendarCellCssClasses => {
const unvailableArray = this.shareDate.unavailableDates;
const reservedArray = this.shareDate.reservedDate;
let day = 'freeDate';
for (const element of reservedArray) {
if (date.getFullYear() === element.getFullYear() && date.getMonth() === element.getMonth() &&
date.getDate() === element.getDate()) {
day = 'prenotation';
return day;
}
}
for (const element of unvailableArray) {
if (date.getFullYear() === element.getFullYear() && date.getMonth() === element.getMonth() &&
date.getDate() === element.getDate()) {
day = 'unavailable';
return day;
}
}
return day;
};
component.ts
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Clicca sul calendario</mat-label>
<input matInput readonly [matDatepicker]="picker" [(ngModel)]="this.shareDate.myFormattedDate" (dateChange)="openDialog($event)">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker [dateClass]="dateClass" #picker> </mat-datepicker>
</mat-form-field>
Related
I'm working with a customElement using Shadow DOM like:
<hello-there><b>S</b>amantha</hello-there>
And the innerHTML (generated by lit/lit-element in my case) is something like:
<span>Hello <slot></slot>!</span>
I know that if const ht = document.querySelector('hello-there') I can call .innerHTML and get <b>S</b>amantha and on the shadowRoot for ht, I can call .innerHTML and get <span>Hello <slot></slot>!</span>. But...
The browser essentially renders to the reader the equivalent of if I had expressed (without ShadowDOM) the HTML <span>Hello <b>S</b>amantha!</span>. Is there a way to get this output besides walking all the .assignedNodes, and substituting the slot contents for the slots? Something like .slotRenderedInnerHTML?
(update: I have now written code that does walk the assignedNodes and does what I want, but it seems brittle and slow compared to a browser-native solution.)
class HelloThere extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = '<span>Hello <slot></slot>!</span>';
}
}
customElements.define('hello-there', HelloThere);
<hello-there><b>S</b>amantha</hello-there>
<div>Output: <input type="text" size="200" id="output"></input></div>
<script>
const ht = document.querySelector('hello-there');
const out = document.querySelector('#output');
</script>
<button onclick="out.value = ht.innerHTML">InnerHTML hello-there</button><br>
<button onclick="out.value = ht.outerHTML">OuterHTML hello-there</button><br>
<button onclick="out.value = ht.shadowRoot.innerHTML">InnerHTML hello-there shadow</button><br>
<button onclick="out.value = ht.shadowRoot.outerHTML">OuterHTML hello-there shadow (property does not exist)</button><br>
<button onclick="out.value = '<span>Hello <b>S</b>amantha!</span>'">Desired output</button>
Since there doesn't seem to be a browser-native way of answering the question (and it seems that browser developers don't fully understand the utility of seeing a close approximation to what the users are approximately seeing in their browsers) I wrote this code.
Typescript here, with pure-Javascript in the snippets:
const MATCH_END = /(<\/[a-zA-Z][a-zA-Z0-9_-]*>)$/;
/**
* Reconstruct the innerHTML of a shadow element
*/
export function reconstruct_shadow_slot_innerHTML(el: HTMLElement): string {
return reconstruct_shadow_slotted(el).join('').replace(/\s+/, ' ');
}
export function reconstruct_shadow_slotted(el: Element): string[] {
const child_nodes = el.shadowRoot ? el.shadowRoot.childNodes : el.childNodes;
return reconstruct_from_nodeList(child_nodes);
}
function reconstruct_from_nodeList(child_nodes: NodeList|Node[]): string[] {
const new_values = [];
for (const child_node of Array.from(child_nodes)) {
if (!(child_node instanceof Element)) {
if (child_node.nodeType === Node.TEXT_NODE) {
// text nodes are typed as Text or CharacterData in TypeScript
new_values.push((child_node as Text).data);
} else if (child_node.nodeType === Node.COMMENT_NODE) {
const new_data = (child_node as Text).data;
new_values.push('<!--' + new_data + '-->');
}
continue;
} else if (child_node.tagName === 'SLOT') {
const slot = child_node as HTMLSlotElement;
new_values.push(...reconstruct_from_nodeList(slot.assignedNodes()));
continue;
} else if (child_node.shadowRoot) {
new_values.push(...reconstruct_shadow_slotted(child_node));
continue;
}
let start_tag: string = '';
let end_tag: string = '';
// see #syduki's answer to my Q at
// https://stackoverflow.com/questions/66618519/getting-the-full-html-for-an-element-excluding-innerhtml
// for why cloning the Node is much faster than doing innerHTML;
const clone = child_node.cloneNode() as Element; // shallow clone
const tag_only = clone.outerHTML;
const match = MATCH_END.exec(tag_only);
if (match === null) { // empty tag, like <input>
start_tag = tag_only;
} else {
end_tag = match[1];
start_tag = tag_only.replace(end_tag, '');
}
new_values.push(start_tag);
const inner_values: string[] = reconstruct_from_nodeList(child_node.childNodes);
new_values.push(...inner_values);
new_values.push(end_tag);
}
return new_values;
}
Answer in context:
const MATCH_END = /(<\/[a-zA-Z][a-zA-Z0-9_-]*>)$/;
/**
* Reconstruct the innerHTML of a shadow element
*/
function reconstruct_shadow_slot_innerHTML(el) {
return reconstruct_shadow_slotted(el).join('').replace(/\s+/, ' ');
}
function reconstruct_shadow_slotted(el) {
const child_nodes = el.shadowRoot ? el.shadowRoot.childNodes : el.childNodes;
return reconstruct_from_nodeList(child_nodes);
}
function reconstruct_from_nodeList(child_nodes) {
const new_values = [];
for (const child_node of Array.from(child_nodes)) {
if (!(child_node instanceof Element)) {
if (child_node.nodeType === Node.TEXT_NODE) {
new_values.push(child_node.data);
} else if (child_node.nodeType === Node.COMMENT_NODE) {
const new_data = child_node.data;
new_values.push('<!--' + new_data + '-->');
}
continue;
} else if (child_node.tagName === 'SLOT') {
const slot = child_node;
new_values.push(...reconstruct_from_nodeList(slot.assignedNodes()));
continue;
} else if (child_node.shadowRoot) {
new_values.push(...reconstruct_shadow_slotted(child_node));
continue;
}
let start_tag = '';
let end_tag = '';
const clone = child_node.cloneNode();
// shallow clone
const tag_only = clone.outerHTML;
const match = MATCH_END.exec(tag_only);
if (match === null) { // empty tag, like <input>
start_tag = tag_only;
} else {
end_tag = match[1];
start_tag = tag_only.replace(end_tag, '');
}
new_values.push(start_tag);
const inner_values = reconstruct_from_nodeList(child_node.childNodes);
new_values.push(...inner_values);
new_values.push(end_tag);
}
return new_values;
}
class HelloThere extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = '<span>Hello <slot></slot>!</span>';
}
}
customElements.define('hello-there', HelloThere);
<hello-there><b>S</b>amantha</hello-there>
<div>Output: <input type="text" size="200" id="output"></input></div>
<script>
const ht = document.querySelector('hello-there');
const out = document.querySelector('#output');
</script>
<button onclick="out.value = ht.innerHTML">InnerHTML hello-there</button><br>
<button onclick="out.value = ht.outerHTML">OuterHTML hello-there</button><br>
<button onclick="out.value = ht.shadowRoot.innerHTML">InnerHTML hello-there shadow</button><br>
<button onclick="out.value = ht.shadowRoot.outerHTML">OuterHTML hello-there shadow (property does not exist)</button><br>
<button onclick="out.value = reconstruct_shadow_slot_innerHTML(ht)">Desired output</button>
Reference used:
https://stackblitz.com/edit/github-yn8zve-xl2vcs?file=src%2Fapp%2Fexamples%2F01-single-selection-example%2Fsingle-selection-example.component.ts
Values are loaded into countrydropdown but after I am clicking it for first time . After that all is working fine . only concern is value not loading at the page loads .
here is the typescript:
ngOnInit(): void {
this.orgNameSearchInit();
this.getCountryCodes();
// load the initial bank list
console.log("inside nginit ")
this.countryArrFilterDropdown.next(this.countryArray.slice());
//to listen any change in search box
this.searchFormControl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterCodes();
});
}
protected filterCodes() {
if (!this.countryArray) {
return;
}
// get the search keyword
let search = this.searchFormControl.value;
console.log("searched value: "+ search);
if (!search) {
console.log(this.countryArrFilterDropdown.next(this.countryArray.slice()));
this.countryArrFilterDropdown.next(this.countryArray.slice());
return;
} else {
search = search.toLowerCase();
}
console.log("after ifelse");
// filter the Country
this.countryArrFilterDropdown.next(
this.countryArray.filter((countrys) => countrys.countryName.toLowerCase().indexOf(search) > -1)
// this.banks.filter((bank) => bank.name.toLowerCase().indexOf(search) > -1)
);
}
ngAfterViewInit(): void {
this.setInitialValue();}
here is the html :
<div class="col-sm-2">
<mat-form-field>
<mat-select [formControl]="testFormControl" placeholder="Code" #singleSelect>
<mat-option>
<ngx-mat-select-search [formControl]="searchFormControl" placeholderLabel="Search"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let countryArrFilterDropdowns of countryArrFilterDropdown | async"
[value]="countryArrFilterDropdowns.countryCode">
{{countryArrFilterDropdowns.countryName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
I have a very simple pug file:
for item in itemList
form(method='post', action='/change')
table
tr
td(width=100)
td(width=200)
| #{item.name}
input(type='hidden', name='field' value=item.name)
input(type='hidden', name='style' value='doublevalue')
td(width=100)
input(type='number', name='value' min=-20.0 max=80.00 step=0.01 value=+item.value)
td(width=100)
input(type='submit', value='Update')
p end
As you can see it produces a few trivial forms like this:
(Each form is one 'line' which is a simple table.)
(On the script side, it just reads each 'line' from a MySQL table, there are 10 or so of them.)
So on the www page, the user either
types in new number (say "8")
or clicks the small arrows (say Up, changing it to 7.2 in the example)
then the user must
click submit
and it sends the post.
Quite simply, I would like it to be that when the user
clicks a small arrows (say Up, changing it to 7.2 in the example)
it immediately sends a submit-post.
How do I achieve this?
(It would be fine if the send happens, any time the user types something in the field, and/or, when the user clicks the Small Up And Down Buttons. Either/both is fine.)
May be relevant:
My pug file (and all my pug files) have this sophisticated line of code as line 1:
include TOP.pug
And I have a marvellous file called TOP.pug:
html
head
style.
html {
font-family: sans-serif
}
td {
font-family: monospace
}
body
I have a solution with javascript.
// check if there are input[type="number"] to prevent errors
if (document.querySelector('input[type="number"]')) {
// add event for each of them
document.querySelectorAll('input[type="number"]').forEach(function(el) {
el.addEventListener('change', function (e) {
// on change submit the parent (closest) form
e.currentTarget.closest('form').submit()
});
});
}
Actually it is short but if you want to support Internet Explorer you have to add the polyfill script too. Internet Explorer does not support closest() with this snippet below we teach it.
// polyfills for matches() and closest()
if (!Element.prototype.matches)
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
Ajax form submit to node.js
If you are interested in an ajax solution I put some code below just to blow your mind ;-) It should work instantly, I use it on one of my sites. You could use jQuery and save lines of code but I like it pure. (The ajax function and polyfills are utils so paste it anywhere)
HTML (example)
<form>
<input type="hidden" name="field" value="field1">
<input type="hidden" name="style" value="style1">
<input type="number" name="value">
<input type="submit" value="update">
</form>
<form>
<input type="hidden" name="field" value="field2">
<input type="hidden" name="style" value="style2">
<input type="number" name="value">
<input type="submit" value="update">
</form>
Javascript: event listener and prepare ajax call (note the callbacks).
// check if there are forms to prevent errors
if (document.querySelector('form')) {
// add submit event for each form
document.querySelectorAll('form').forEach(function (el) {
el.addEventListener('submit', function (e) {
e.currentTarget.preventDefault();
submitData(e.currentTarget);
});
});
}
// check if there are input[type="number"] to prevent errors
if (document.querySelector('input[type="number"]')) {
// add change event for each of them
document.querySelectorAll('input[type="number"]').forEach(function (el) {
el.addEventListener('change', function (e) {
submitData(e.currentTarget.closest('form'));
});
});
}
// collect form data and send it
function submitData(form) {
// send data through (global) ajax function
ajax({
url: '/change',
method: 'POST',
data: {
field: form.querySelector('input[name="field"]').value,
style: form.querySelector('input[name="style"]').value,
value: form.querySelector('input[name="value"]').value,
},
// callback on success
success: function (response) {
// HERE COMES THE RESPONSE
console.log(response);
// error is defined in (node.js res.json({error: ...}))
if (response.error) {
// make something red
form.style.border = '1px solid red';
}
if (!response.error) {
// everything ok, make it green
form.style.border = '1px solid green';
}
// remove above styling
setTimeout(function () {
form.style.border = 'none';
}, 1000);
},
// callback on error
error: function (error) {
console.log('server error occurred: ' + error)
}
});
}
As told javascript utils (paste it anywhere like a library)
// reusable ajax function
function ajax(obj) {
let a = {};
a.url = '';
a.method = 'GET';
a.data = null;
a.dataString = '';
a.async = true;
a.postHeaders = [
['Content-type', 'application/x-www-form-urlencoded'],
['X-Requested-With', 'XMLHttpRequest']
];
a.getHeaders = [
['X-Requested-With', 'XMLHttpRequest']
];
a = Object.assign(a, obj);
a.method = a.method.toUpperCase();
if (typeof a.data === 'string')
a.dataString = encodeURIComponent(a.data);
else
for (let item in a.data) a.dataString += item + '=' + encodeURIComponent(a.data[item]) + '&';
let xhReq = new XMLHttpRequest();
if (window.ActiveXObject) xhReq = new ActiveXObject('Microsoft.XMLHTTP');
if (a.method == 'GET') {
if (typeof a.data !== 'undefined' && a.data !== null) a.url = a.url + '?' + a.dataString;
xhReq.open(a.method, a.url, a.async);
for (let x = 0; x < a.getHeaders.length; x++) xhReq.setRequestHeader(a.getHeaders[x][0], a.getHeaders[x][1]);
xhReq.send(null);
}
else {
xhReq.open(a.method, a.url, a.async);
for (let x = 0; x < a.postHeaders.length; x++) xhReq.setRequestHeader(a.postHeaders[x][0], a.postHeaders[x][1]);
xhReq.send(a.dataString);
}
xhReq.onreadystatechange = function () {
if (xhReq.readyState == 4) {
let response;
try {
response = JSON.parse(xhReq.responseText)
} catch (e) {
response = xhReq.responseText;
}
//console.log(response);
if (xhReq.status == 200) {
obj.success(response);
}
else {
obj.error(response);
}
}
}
}
// (one more) polyfill for Object.assign
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, 'assign', {
value: function assign(target, varArgs) {
// .length of function is 2
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
// polyfills for matches() and closest()
if (!Element.prototype.matches)
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) {
Element.prototype.closest = function (s) {
var el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
In node.js (e.g. express route)
// the route in node.js
app.post('/change', (req, res) => {
// your logic here
let field = req.body.field;
let style = req.body.style;
let value = req.body.value;
// ...
// response result
res.json({
databaseError: false, // or true
additionalStuff: 'message, markup and other things ...',
});
});
I have a form in weather that would have had the condition User add as many lines he needs. He clicks a button and an input is added below the other.
I can do this using jQuery, but I would prefer to use the resources of Meteor. Is it possible to do?
Yes it is, here is an example from one of my apps using the underscore package
In the main template:
<template name="ask">
{{#each answerArray}}
{{>answer}}
{{/each}}
<button id="addItem">Add item</button>
</template>
<template name="answer">
<div class="input-group pt10">
<input class="form-control answer" maxlength="30" placeholder="Answer (max 30 chars)" name="answer" />
<span class="input-group-btn">
<button class="btn btn-danger delButton" id="{{id}}" data-id="{{id}}" type="button">Delete</button>
</span>
</div>
</template>
In the js file:
Template.ask.created = function () {
Session.set('action', 'ask');
answerArray = [ //adding at least two items but it could also be empty
{
id: Random.id(), //using this to give an unique id to the control
value: ''
},
{
id: Random.id(),
value: ''
}
];
Session.set('answerArr', answerArray);
}
And the click event:
Template.ask.events = {
'click #addItem': function () {
var answerArray = Session.get('answerArr');
answerArray.push({
id: Random.id() //just a placeholder, you could put any here
});
Session.set('answerArr', answerArray);
}
}
And finally the helper:
Template.ask.helpers({
answerArray: function () {
var answerArray = Session.get("answerArr")
while (answerArray.length < 2) { //i chose to have it between 2 and 6, you can remove these
answerArray.push({
id: Random.id()
})
}
while (answerArray.length > 6) { // maximum
answerArray.pop();
}
Session.set('answerArr', answerArray);
return answerArray;
}
}
This will reactively increase the number of inputs. After that, if you want to process the inputs you could do the following, on a submit form event or button click:
'click #saveQ': function (e) {
e.preventDefault();
var arr = [];
_.each($('.answer'), function (item) {
if ($(item).val() != '')
arr.push({
answer: $(item).val(), //this you customize for your own purposes
number: 0
})
});
And also if you want to delete an input from the page you can use:
Template.answer.events = {
'click .delButton': function (e) {
var thisId = $(e.target).attr("id");
var answerArray = Session.get('answerArr');
var filteredArray = _.filter(answerArray, function (item) {
return item.id != thisId;
});
Session.set('answerArr', filteredArray);
}
}
I want to capture the event of a user pressing enter on an input of type="text" when they are filling out a form. This is done all over the web, yet the answer eludes me.
This is what I have so far:
In the html file, I have a text input like so:
<input type="text" size=50 class="newlink">
In the Javascript file, I am trying to capture the the user pressing enter to effectively submit the form. I am then grabbing the text from the input and going to stash it in the database:
Template.newLink.events = {
'submit input.newLink': function () {
var url = template.find(".newLink").value;
// add to database
}
};
The submit event is emitted from forms, not single input elements.
The built in event map for meteor is documented here: http://docs.meteor.com/#eventmaps.
You'll have to listen for a keyboard event (keydown, keypress, keyup). Within the event handler, check, if it's the return/enter key (Keycode 13), and proceed on success.
Template.newLink.events = {
'keypress input.newLink': function (evt, template) {
if (evt.which === 13) {
var url = template.find(".newLink").value;
// add to database
}
}
};
You could look into how this is achieved in the todos example (client/todos.js).
It uses a generic event handler for input fields (as seen below). You can browse the rest of the code for usage.
////////// Helpers for in-place editing //////////
// Returns an event map that handles the "escape" and "return" keys and
// "blur" events on a text input (given by selector) and interprets them
// as "ok" or "cancel".
var okCancelEvents = function (selector, callbacks) {
var ok = callbacks.ok || function () {};
var cancel = callbacks.cancel || function () {};
var events = {};
events['keyup '+selector+', keydown '+selector+', focusout '+selector] =
function (evt) {
if (evt.type === "keydown" && evt.which === 27) {
// escape = cancel
cancel.call(this, evt);
} else if (evt.type === "keyup" && evt.which === 13 ||
evt.type === "focusout") {
// blur/return/enter = ok/submit if non-empty
var value = String(evt.target.value || "");
if (value)
ok.call(this, value, evt);
else
cancel.call(this, evt);
}
};
return events;
};
I used this js function once to suppress the user using the return key in the text field to submit the form data. Perhaps you could modify it to suit the capture?
function stopRKey(evt) { // Stop return key functioning in text field.
var evt = (evt) ? evt : ((event) ? event : null);
var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
if ((evt.keyCode == 13) && (node.type=="text")) { return false; }
}
document.onkeypress = stopRKey;
You can also use event.currentTarget.value
Template.newLink.events = {
'keypress input.newLink': function (evt) {
if (evt.which === 13) {
var url = event.currentTarget.value;
// add to database
}
}
};