I'm using Angular 10 and ng-zorro and I am using nz-upload component, and I have a question:
Why beforeUpload is called equal to files uploaded? if we have a nzFileUpload[] parameter, its like a repetition not wanted
My .html code:
<div>
<nz-upload
ngDefaultControl
nzType="drag"
(click)="form.get('files').markAsTouched()"
[nzDirectory]="false"
[nzBeforeUpload]="beforeUpload"
[nzDisabled]="false"
[nzLimit]="fileMaxQuantity"
[nzSize]="0"
[nzListType]="'text'"
[nzMultiple]="true"
[nzShowUploadList]="false"
[nzShowButton]="false"
(nzChange)="handleChange($event)"
>
<p class="ant-upload-drag-icon" style="margin-bottom: unset;">
<i nz-icon nzType="cloud-upload"></i>
</p>
<p class="ant-upload-text">{{ '::FileUpload:FilesUploadMessage' | abpLocalization }}
</p>
<p class="ant-upload-hint">
{{ '::FileUpload:FilesUploadHint' | abpLocalization }}
</p>
</nz-upload>
<mat-error *ngIf="form.get('files').hasError('required') && form.get('files').touched">
{{ '::FileUpload:FilesFieldRequired' | abpLocalization}}
</mat-error>
</div>
And my .ts (beforeupload):
beforeUpload = (singleFile: File, fileList: File[]): boolean => {
// if (this.form.controls.files as FormArray)
const fileNames = this.form.controls.fileNames.value as [];
if (fileNames.length === this.fileMaxQuantity) {
this.snackBarService.warning(this.localizationService.instant('::FileUpload:NumberFilesExceedsAllowed'), true);
return false;
} else {
for (let i = 0; i <= fileList.length; i++) {
const file = fileList[i];
// _.each(fileList, (file) => {
if (this.form.controls.fileNames.value.length === this.fileMaxQuantity) {
this.snackBarService.warning(this.localizationService.instant('::FileUpload:NumberFilesExceedsAllowed'));
break;
// return false;
} else {
const tempStackSize = this.actualFileStackSize + file.size;
if (file.size > this.fileMaxSize || tempStackSize > this.fileMaxSize) {
this.snackBarService.warning(this.localizationService.instant('::FileUpload:FileTooHeavy'), true);
return false;
} else if ( !this.fileList.some( p => p.name === file.name ) ) {
const ext = this.extensionPipe.transform(file.name);
let control: FormControl;
(this.form.controls.files as FormArray).push(new FormControl(file.name));
(!this.regexWithExt.test(file.name)) ?
this.fileListRequired.push(true) : // debe cambiar filename
this.fileListRequired.push(false);
control = new FormControl(file.name.replace(ext, ''),
[ Validators.pattern(this.regex),
Validators.required,
Validators.maxLength(this.fileNameMaxLength - ext.length),
this.fileNameValidator()]);
control.markAllAsTouched();
(this.form.controls.fileNames as FormArray).push(control);
// fileNames.push(file.name);
this.fileList.push(file);
this.actualFileStackSize = tempStackSize;
}
}
}
// fileList.forEach((file) => {
// });
}
return false;
}
From the above, the problem is that this function beforeUpload is called equal to list size of files.
Related
I have a loader function called getBlogData which is like this:
import { getFromCacheOrApi } from 'Base'
const getBlogData = async () => {
const { pathname } = { pathname: "" }
var url = '/blog/data?'
let matches = /\/blog(\/\d+)?\/?$/.exec(pathname)
if (matches != null) {
const pageNumber = matches[1]
if (pageNumber !== undefined) {
url += `&pageNumber=${pageNumber.replace('/', '')}`
}
}
else {
const secondSegments = ['category', 'tag', 'author', 'search']
if (pathname.split('/').length >= 2 && !secondSegments.includes(pathname.split('/')[2])) {
response.status = 404
return
}
for (let i = 0; i < secondSegments.length; i++) {
const segment = secondSegments[i]
if (pathname.startsWith(`/blog/${segment}`)) {
matches = new RegExp(`(?<=\\/blog\\/${segment}\\/)[^/]+\\/?(\\d+)?\\/?$`).exec(pathname)
if (matches == null) {
response.status = 404
return
}
else {
url += `&${segment}=${encodeURI(matches[0].split('/')[0])}`
const pageNumber = matches[1]
if (pageNumber !== undefined) {
url += `&pageNumber=${pageNumber}`
}
break
}
}
}
}
url = url.replace('?&', '?')
const data = await getFromCacheOrApi(url)
// console.log(params, response.status)
// if (pageNumber && isNaN(pageNumber)) {
// console.log(pageNumber, isNaN(pageNumber))
// response.status = 400
// return
// }
const { seoParameters } = data
return data
}
export default getBlogData
This function is only used in my page which is inside app directory in Next 13, which means that it's a server component, and I don't want to change it to a client component.
However, I need to access request data, in this particular case, the path of the URL.
How can I get that?
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>
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 found out that we can leverage an #index in an #each helper, but that doesn't seem to help much.
I am trying to implement optional zebra striping in some handlebar templates.
{#if ((#index % 2) == 0) }}
<tr class="darkRow">
{{else}}
<tr>
{{/if}}
But when I compile the template the error is
>> Error: Parse error on line 3:
{{#if ((#index % 2) == 0)
>> ----------------------^
>> Expecting 'CLOSE', 'CLOSE_UNESCAPED', 'STRING', 'INTEGER', 'BOOLEAN', 'ID', 'DATA', 'SEP', got 'INVALID'
Is it possible to do something like this?
I've implemented my own solution by modifying the each helper. Here's my new each code in the handlebars.js file
instance.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) {
// For zebra tables
data.zebra = (((i+1)%2) == 0) ? "even" : "odd";
// end mod
data.index = i;
data.first = (i === 0);
data.last = (i === (context.length-1));
}
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) {
data.key = key;
data.index = i;
data.first = (i === 0);
}
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
And I use it in {{#each xx}} loops in my templates by putting the class="{{#zebra}}" on my divs or trs.
Hope this helps others!
I try to setup a dev environnement using brunch and handlebars. The handlebars-brunch package is stored in my node_modules and handlebars.runtime.js included in my vendor.js file. I've defined this template :
hello.template:
<p>Hello {{name}}</p>
And add these lines to my config.coffee :
templates:
defaultExtension: 'handlebars'
joinTo: 'app.js'
And as a proof that it works, I can see in my app.js the following lines :
;var __templateData = Handlebars.template(function Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression;
buffer += "<p>Hello ";
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
buffer += escapeExpression(stack1)
+ "</p>\r\n\r\n";
return buffer;
});
if (typeof define === 'function' && define.amd) {
define([], function() {
return __templateData;
});
} else if (typeof module === 'object' && module && module.exports) {
module.exports = __templateData;
} else {
__templateData;
}
But when i invoke Handlebars.templates.hello like this :
var tpl = Handlebars.templates.hello;
var html = tpl ( { name: "ok" } );
console.log ( tpl );
I get this error : Cannot read property 'hello' of undefined. So Handlebars is defined but not templates. My dependencies inclusion seems fine too as my main function is located after everything else, like this:
[...]
if (typeof define === 'function' && define.amd) {
define([], function() {
return __templateData;
});
} else if (typeof module === 'object' && module && module.exports) {
module.exports = __templateData;
} else {
__templateData;
}
;( function ( $, document, window ) {
var tpl = Handlebars.templates.hello;
var html = tpl ( { name: "ok" } );
console.log ( tpl );
} ( jQuery, document, window ) );
Any idea/suggestion?
Brunch uses modules. It doesn't assign global variables or something.
require('name')
if your file was app/name.js