React Warning: Unknown prop `valueAsNumber` on <input> tag - forms

I have a component that wraps an <input type=number>.
Here is my JSX:
function InputNumber(props) {
return (<input
type="number"
valueAsNumber={props.value}
onChange={e => props.onChange(e.target.valueAsNumber)}
step={props.step}
/>);
}
This compiles to the following JS:
function InputNumber(props) {
return (React.createElement("input", {type: "number", valueAsNumber: props.value, onChange: function (e) { return props.onChange(e.target.valueAsNumber); }, step: props.step}));
}
React is giving me the following warning:
Warning: Unknown prop valueAsNumber on tag. Remove this prop from the element.
It seems to work fine if I read and write from element.valueAsNumber in the DOM, so why doesn't React know about this property?

To use non-standard attributes on React components without having them stripped, you must follow HTML 5 standard and prefix them with "data-" and don't use camel case. So yours would be:
data-value-as-number={props.value}

As Jeff McCloud said, you are going to use non standard html attributes. Your function may be rewritten like this:
function InputNumber(props) {
return (<input
type="number"
data-valueAsNumber={props.value}
onChange={e => props.onChange(e.target.dataset.valueAsNumber)}
step={props.step}
/>);
}

According to the DOM Elements Doc, valueAsNumber is not a valid attribute. This article also confirms that unsupported attributes are stripped.
React does not yet recognize the attribute you specified. This will likely be fixed in a future version of React. However, React currently strips all unknown attributes, so specifying them in your React app will not cause them to be rendered.
You might want to check out this repo instead:
react-numeric-input
Number input component that can replace the native number input which is not yet very well supported and where it is, it does not have the same appearance across the browsers. Additionally this component offers more flexible options and can be used for any values (differently formatted representations of the internal numeric value).

Related

2019, Chrome 76, approach to autocomplete off

There are are few posts out there about this. You spend hours going through each answer, testing, reading comments, to find that there is no solution. What have you done in 2019, Chrome 76, that works?
Update, January 2020: It appears that as of Chrome 79, Autocomplete (as defined here) no longer treats autocomplete="some-unrecognised-value" as equal to autocomplete="on", so autocomplete="nope" or similar is now effective at disabling both Autocomplete and Autofill.
Update, April 2020: They changed it again. As of Chrome 81, autocomplete="some-unrecognised-value" is no longer effective at disabling the Autocomplete mechanism. However, Autofill now seems to be a lot more conservative than it was before - it still doesn't follow the spec (a field with name="email" and autocomplete="off" will still receive Autofill suggestions) but it doesn't offer up spurious address fragments on random form fields. My recommendation right now would therefore be to use autocomplete="off". If you want to do that on a field named email, you're probably out of luck though :-(
TL,DR: There appears to be no setting for the autocomplete attribute that will reliably turn off all autocomplete dropdowns. However, the circumstances that have led to this are quite convoluted and worth documenting, to hopefully clear up the masses of conflicting advice...
There are two distinct mechanisms present in current (76.0.3809.132) versions of Chrome, which we'll refer to as Autofill and Autocomplete (not necessarily their official names):
Autofill
The Autofill feature attempts to fill in forms using the address information stored in your browser settings. It can be identified by the "Manage addresses..." option (or similar) at the bottom of the dropdown. This feature does not honour autocomplete="off" or autocomplete="false", as a deliberate decision on the part of the Chrome developers.
In a statement outlining this decision, zkoch offered this workaround:
In cases where you really want to disable autofill, our suggestion at
this point is to utilize the autocomplete attribute to give valid,
semantic meaning to your fields. If we encounter an autocomplete
attribute that we don't recognize, we won't try and fill it.
As an example, if you have an address input field in your CRM tool
that you don't want Chrome to Autofill, you can give it semantic
meaning that makes sense relative to what you're asking for: e.g.
autocomplete="new-user-street-address". If Chrome encounters that, it
won't try and autofill the field.
This is the basis of attempted solutions such as autocomplete="nope"; the Autofill mechanism will skip any fields with autocomplete attribute values it doesn't recognise.
The code that implements this decision, for the record: https://chromium.googlesource.com/chromium/src/+/refs/tags/78.0.3903.1/components/autofill/core/browser/form_structure.cc#1218
Autocomplete
The Autocomplete feature provides a dropdown of previously-submitted values from this form field. This dropdown does not have a "Manage addresses..." option. Autocomplete does honour the autocomplete="off" or autocomplete="false" attribute; any other value (including 'invalid' ones such as autocomplete="nope") will leave it enabled.
Conclusion
Autocompletion dropdowns cannot be turned off through the autocomplete dropdown; any value that disables Autofill will leave Autocomplete enabled, and vice versa. Anyone who thinks they've found a working solution (either through autocomplete or some other method such as CSS hacks) should check that it works against both mechanisms.
Unfortunately it's probably going to be an uphill struggle to convince Chrome's developers that this is broken. The developers of Autofill apparently believe that they made a calculated decision to break autocomplete="off" while offering web developers an alternative; that alternative is broken, for more subtle reasons than they realise. From their perspective, the resulting howls of protest are coming from disgruntled developers too lazy to jump through one little hoop and update their autocomplete="off" attributes. In all the noise, the message isn't getting through: the hoop is broken.
Try using type="search" instead of "text" for your input field, I've done this several time and it works for me.
As of Dec 6, 2019, with Chrome v78.x
Standard methods like autocomplete="off" are now working almost fine for the latest versions of Chrome. Except for this one:
This thing is a real bummer because it doesn't only disrespect the standard/non-standard values like "nope" but there's literally no way to turn this off unless the input is not even remotely related with "addressy" terms.
How on earth we could possibly display address-related input fields without using address-related words? Here comes the easiest solution ever.
Make sure the input element's name and id don't include any address-related terms. Attributes like id="input-street" or name="destination-zip" are big no-no.
This is the most crucial part: If you are required to use any human-readable address terms for the text input or any of its adjacent elements, insert the "invisible" zero width joiner (‌) between the letters of the said term. In this way, we can fool the AI capability of Chrome and bypass its strict autocompletion behavior.
Some working examples:
<input id="input-stret" placeholder="S‌treet" autocomplete="off">
<form action="/action_page.php">
<label for="product-addres">Product A‌ddress</label>
<input name="addres" id="product-addres" autocomplete="off">
</form>
And there you go. No more pesky menus for managing addresses, nor any regular autocompletion menus.
As gasman's answer explains, both the autofill and autocomplete features must be disabled, which doesn't seem possible on a single input.
The only working solution I've found is to setting autocomplete="off" on the input and add hidden fake inputs before the real input that fool autofill, like so:
<input name="Fake_Username" id="Fake_Username" type="text" style="display:none">
<input name="Fake_Password" id="Fake_Password" type="password" style="display:none">
<input name="NameInput" id="NameInput" type="text" autocomplete="off">
* This answer is incorrect. I've published a better (but uglier) solution as a new answer and kept this answer since some parts may still be useful. If that's not how to deal with incorrect answers on stackoverflow, feel free to delete this one *
Consider using autocomplete=<nonce>, where <nonce> is unique per field and across page loads.
For example, if a field is the N-th field created after the page was requested at timestamp TS, its <nonce> can be chosen to be nope_<TS>_<N>.
Effect on autocomplete: since <nonce> is a custom value for autocomplete, chromium does not activate the autocomplete function (see form_structure.cc).
Effect on autofill: chromium recognizes a field by comparing its fingerprint with those of earlier encountered fields (see form_field_data.cc). If recognized it may offer a list of remembered values. The fingerprints contain the value of the autocomplete attribute. Since no two nonces are equal, no field is recognized.
Notes:
The terms autocomplete and autofill as used here are swapped compared to gasman's reply.
All fields should be created dynamically on the client-side (unless you are willing to not have the page cached).
Disabling autofill:
Set autocomplete attribute to a non-standard value, e.g. "nope".
Disabling autocomplete:
The autocomplete function stores field names and their values when a form is submitted.
There's (almost, see note 1) nothing sensible to be done to prevent storage except setting autocomplete to "off"/"false" (see why).
Unfortunately that's not an option as it would enable autofill.
However it's possible to prevent retrieval of previous values by appending "!<nonce>" to the field names, where <nonce> is unique per page load
(thus making field names unrecognizable).
On the client side this can be achieved by something like the following line of javascript (upon page load):
Array.prototype.slice.call(document.body.getElementsByTagName('INPUT'))
.forEach(function(elt) { elt.name += '!' + new Date().getTime(); });
On the server side the part (if any) starting at "!" should be dropped from variable names (upon receiving post variables).
PS: this answer is an erratum to my earlier solution which is cleaner but wasn't sufficiently tested and - as gasman rightly pointed out - doesn't work for ordinary forms. This new solution was tested on Chrome Canary 79, does work, has relatively small impact and degrades nicely. Still, I feel guilty about publishing this hack and will feel even more guilty if I ever encounter it in real forms. It is *very* dirty.
Note 1: the only way to prevent storage that does make sense is to not set the name attribute in the first place (or to unset it), which necessitates intercepting the submit event to post the data "manually" (using XMLHttpRequest). Since the question is about forms and this strategy bypasses the traditional form-mechanism I've not elaborated on that approach. It's a nicer solution though.
Addendum: I decided to follow up on note 1 since I really dislike having a non-localized solution. Here's a localized version in vanilla JS that limits all impact to a single spot on the client side. Append it as a script to the document body or put it in the onload handler of the document.
function disableInputSuggestions(form) { // note: code uses ECMA5 features
// tweak the inputs of form
var inputs = Array.prototype.slice.call(form.getElementsByTagName('INPUT'));
var nonce = Date.now();
inputs.forEach(function(input, i) {
input.autocomplete = 'nope'; // prevent autocomplete
input.originalName = input.name || input.id; // to not let this code break form handling of inputs without names (browsers fallback to the id in that case)
input.name = nonce + '_' + i; // prevent autofill (if you're willing to eliminate all input ids first, then clear the name instead)
});
// replace the default submit handler by a custom one
form.onsubmit = function(ev) {
// get the form data using the original variable names
var formData = new FormData();
inputs.forEach(function(input) { formData.set(input.originalName, input.value); });
// submit the form data using XMLHttpRequest (alternatively, use a helper form or temporarily undo the tweaks to form)
var submitter = new XMLHttpRequest();
submitter.open(form.getAttribute('method'), form.getAttribute('action'));
submitter.onreadystatechange = function() {
if(submitter.readyState == 4 && submitter.status == 200) {
// handle the server response, here assuming the default form.target = "_self"
document.open();
document.write(submitter.responseText);
document.close();
}
}
submitter.send(formData);
return false; // prevent submitting form
};
}
disableInputSuggestions(document.forms.myForm); // assumed: the form has id = myForm
In Chrome 91
You need to use a random value, meaning a value that will change each time you load the page.
From the tests that I did, chrome seems to remember any attribute value that it already encountered and will suggest the last seen value for that attribute value the next time. So, if you put autocomplete="nope", chrome will remember that autocomplete="nope" is equal to the last value that you put in autocomplete="nope".
By using a unique random value that chrome has never seen, it won't suggest anything because it has never seen that value.
PHP 7 Example
<input type="text" name="firstname" autocomplete="<?= bin2hex(random_bytes(10)) ?>" />
Limitations
It seems to work on address fields but it has no effect on login fields. I haven't tested with credit card fields.
Chrome version 81.
For me, when input type is TEL, EMAIL or SEARCH, it WORKS with autocomplete='disabled'.
When input type is NUMBER, it WORKS with autocomplete='off'.
But when input type is TEXT .. it may works with autocomplete='off'. If not, it will do with autocomplete='disabled'.
You can try this, perhaps it will work for you (it works in 95% of cases for me) :
// Désactivation de l'autocomplete des input text
function setAutocomplete(val) {
var E = document.getElementsByTagName('INPUT');
for (var i = 0 ; i < E.length ; i++) {
if (E[i].name == 'txt_nom') { console.log('txt_nom', E[i]); }
var type = E[i].type.toUpperCase();
if (E[i].autocomplete != '') { continue; }
if (type == 'HIDDEN') {
//
} else if (type == 'NUMBER') {
E[i].autocomplete = 'off';
} else if ((type == 'TEL') || (type == 'EMAIL') || (type == 'SEARCH')) {
E[i].autocomplete = 'disabled';
} else {
E[i].autocomplete = val;
}
}
}
// Exécution de diverses fonctions à la fin de chaque chargement
window.addEventListener("load", function() {
// Désactivation de l'autocomplete des input text
setAutocomplete('off');
});
try this, I used this little trick and it worked until now (September 2020). i hope this works for a lot of people
--HTML--
<input type="text" name="example" autocomplete="off">
--Javascript--
let elements = document.querySelectorAll('[autocomplete="off"]');
elements.forEach(element => {
element.setAttribute("readonly", "readonly");
element.style.backgroundColor = "inherit";
setTimeout(() => {
element.removeAttribute("readonly");
}, 500);
})
I made a small jQuery plugin that disables any type of autocomplete feature from any browser
It is made to be used on the form tag, it takes few parameters and can be nested with other jQuery methods.
$('#login_form').randomizeFormFields();
It transforms this:
<form id="login_form" action="" method="post">
<input type="text" name="email">
<input type="password" name="secret">
</form>
Into this:
<form id="login_form" action="" method="post">
<input type="text" name="yQoiFZkCrzwWXN3WWgM8Jblby">
<input type="password" name="ono1qamA9CzrH4tW2COoRtFKI">
</form>
It preserves the original names upon submit
// returned post (php example)
array(2) {
["email"]=>
string(16) "email#domain.com"
["secret"]=>
string(19) "supersecretpassword"
}
https://github.com/cicerogeorge/randomize-form-fields
Please fork it if you have ideas
I have tried with autocomplete = "off" and autocomplete = "nope" for EmailId textbox in html form but it is not working for Google Chrome. So I tried with below changes that worked for me.
<input type="email" class="tbx-input" name="Email" style="display:none;">
<input type="email" class="tbx-input" name="Email" id="Email" placeholder=" " required autocomplete="nope">
I couldnt get any of these suggestions to work... so I just made my input a textarea, made cols="1" and disabled adjusting the space.
<textarea style="resize: none;" type='search' class="form-control" rows="1" cols="50" .... >
No more suggestions
autocomplete='nope'
This is the current working solution for Chrome 76.

Uppercase or capital letters only in sap.m.Input controller of UI5

I'm using a sap.m.Input controller in my SAPUI5 application and I would like the input text in this field will be displayed with capital letters / in uppercase only. The actual value is formatted on a backend along with the data validation.
Is there any ready-to-use property to enable the uppercase mode?
I checked the control properties at API Reference, but can't find something similar.
A question extension:
As far as I understand, the alternative solution to JS is to use a CSS property: text-transform: uppercase; and to attach this style to the specific sap.m.Input controller.
Which approach is more preferable from the performance point of view — to use a CSS-based (text-transform) or a JS-based (liveChange) technique?
As per my knowledge, the better solution is to use the CSS:
.sapMInput.myCustomCSSClass .sapMInputBaseInner {
text-transform: uppercase;
}
You could do this with the liveChange event:
<Input liveChange = "onLiveChange"></Input>
In your controller you define the function:
onLiveChange: function(oEvent) {
var input = oEvent.getSource();
input.setValue(input.getValue().toUpperCase());
}
If it is to display your initial value in your Input-control, you can define a formatter which formats your String to uppercase.
Below is a single line of code you need only to write to solve your requirement. No need for controller, just type it in view :). This approach resolves your issue through use of expressions
<Input value = "{ value: '' constraints: { search: '^[A-Z]*$' } }" />
Use of the search constraint means that it will search in the input and check it against the value you set.
'^[A-Z]*$' means capital letters only, this is a regular expression, or regex.
To enable automatic error handling, simply add in 1line of code under the "sap.ui5" section of your manifest.json:
"handleValidation": true,
It will automatically render an error message to guide the user.
Another regex expression others may find useful: '^[A-Za-z]*$', which means you can type only alphabetical letters, uppercase and lowercase.
Here's a useful resource on inline validation: https://sapui5.hana.ondemand.com/#/topic/07e4b920f5734fd78fdaa236f26236d8
... or you can attach a callback to the "validationSuccess" event of a input control and in the callback execute something like this:
var oSrc = oEvent.getSource();
if(oSrc && oSrc.setValue){
oSrc.setValue(oSrc.getValue().toUpperCase());
}

How can I include content of a component in Fabricator Assemble *without* a corresponding attribute, or at block level?

In Fabricator Assemble, I could have a component button.html:
<a class="button">{{text}}</a>
I can use this with the syntax {{>button text='Home'}}. Notably, I have to specify a name for the "text" attribute.
I'm looking to see how I can handle not needing to do that in Assemble. The Literals section of the docs for Handlebars (whiich Fabricator Assemble is built on) highlights an alternative syntax with which I could include a button:
{{>button 'Home'}}
In this example, "Home" is a value without any name for it at all. Handlebars also indicates the following is possible as a basic block:
{{#button}}
<b>Some button content</b>
{{/button}}
Likewise, that content has no name.
I'd like to be able to do this same thing in Fabricator Assemble, but it doesn't seem to have a way for me to include this nameless content. In templates there's {% body %} but that doesn't work here.
In Handlebars, which Fabricator Assemble is based on, all examples for how to recreate this involve JavaScript, which doesn't translate well to Assemble.
What can I do to use {{>button 'Some text'}} or {{#button}}...{{/button}} syntax in Fabricator Assemble? Is this behaviour even available?
In the current version, the easiest way to achieve this is with a custom helper.
Add the following to the helpers option in your gulpfile.js:
default: (value, defaultValue) => {
return value || defaultValue;
},
Then inside a handlebars template:
<div>
{{default varName 'default value'}}
</div>

Pre-populating form fields with model data in Sightly/HTL

I've tried all the HTL context parameters (even 'unsafe'). When I inspect the input, I can see the value intact, but you can't see the value pre-populated in the field. I tried different types of values, different contexts, and different types of input fields. [AEM 6.2]
<input type="email" name="senderEmail" value="${userProfile.email # context='text'}"/>
If the value is rendered in page source and also visible in browser inspector, could it be that it's hidden by some weird CSS? Something like color:transparent
There are many possible causes. I'll pitch in one, to help get you thinking. Is userProfile available via the use api?
I've made this mistake before:
<div data-sly-use.bean="com.beans.Bean">
${bean.value}
</div>
// ... other code
${bean.value}
The "Bean" isn't available later, outside it's host element.
If I understand your question correctly this isn't actually about HTL, but rather about the HTML input element itself. You have an input element with a value attribute set, yet that value is not displaying in the box. If that's correct, then I'd recommend doing some investigation around HTML input value not displaying when set, rather than sightly context issues.
Some possible answer would include css styles hiding the input text or javascript clearing out the values after page load. There are certainly more potential causes, but we'd need to know more about your page to provide a better answer.
To do some of your investigation you can try loading a component with only that input in it and see if that works, that would eliminate any css or js executing elsewhere on the page.

AEM 6.0: Additional parameters when using data-sly-resource?

I am trying to implement something which I hope is relatively straight forward... I have one component (lets call it the wrapper component) which contains another component (lets call it the inner component) inside it via the data-sly-resource tag:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner' }"></div>
I would like to pass in some additional parameters with this tag, specifically a parameter that can be picked up by sightly in the inner component template? I am trying to specify whether the inner templates outer html tag is unwrapped based on a parameter being passed in when the component is called via data-sly-resource.
After experimenting and perusing the sightly documentation, I can't find a way of achieving this.
Does anyone know if this is possible?
Many thanks,
Dave
You can use the Use-API to write and read request attributes if the alternatives proposed here don't work for you.
A quick example of two components where the outer component sets attributes that are then displayed by the inner component:
/apps/siteName/components/
outer/ [cq:Component]
outer.html
inner/ [cq:Component]
inner.html
utils/ [nt:folder]
setAttributes.js
getAttributes.js
/content/outer/ [sling:resourceType=siteName/components/outer]
inner [sling:resourceType=siteName/components/inner]
/apps/siteName/components/outer/outer.html:
<h1>Outer</h1>
<div data-sly-use="${'../utils/setAttributes.js' # foo = 1, bar = 2}"
data-sly-resource="inner"></div>
/apps/siteName/components/inner/inner.html:
<h1>Inner</h1>
<dl data-sly-use.attrs="${'../utils/getAttributes.js' # names = ['foo', 'bar']}"
data-sly-list="${attrs}">
<dt>${item}</dt> <dd>${attrs[item]}</dd>
</dl>
/apps/siteName/components/utils/setAttributes.js:
use(function () {
var i;
for (i in this) {
request.setAttribute(i, this[i]);
}
});
/apps/siteName/components/utils/getAttributes.js:
use(function () {
var o = {}, i, l, name;
for (i = 0, l = this.names.length; i < l; i += 1) {
name = this.names[i];
o[name] = request.getAttribute(name);
}
return o;
});
Resulting output when accessing /content/outer.html:
<h1>Outer</h1>
<div>
<h1>Inner</h1>
<dl>
<dt>bar</dt> <dd>2</dd>
<dt>foo</dt> <dd>1</dd>
</dl>
</div>
As commented by #AlasdairMcLeay, this proposed solution has an issue in case the inner component is included multiple times on the request: the subsequent instances of the component would still see the attributes set initially.
This could be solved by removing the attributes at the moment when they are accessed (in getAttributes.js). But this would then again be a problem in case the inner component is split into multiple Sightly (or JSP) files that all need access to these attributes, because the first file that accesses the request attributes would also remove them.
This could be further worked-around with a flag telling wether the attributes should be removed or not when accessing them... But it also shows why using request attributes is not a good pattern, as it basically consists in using global variables as a way to communicate among components. So consider this as a work-around if the other two solutions proposed here are not an option.
There is a newer feature that request-attributes can be set on data-sly-include and data-sly-resource :
<sly data-sly-include="${ 'something.html' # requestAttributes=amapofattributes}" />
Unfortunately it doesn't seem to be possible to construct a Map with HTL (=Sightly) expressions, and I don't see a way to read a request attribute from HTL, so you still need some Java/Js code for that.
unfortunately, no. there is no way to extend sightly functionality. you cannot add new data-sly attributes or modify existing ones. The best you can do is write your own helper using the USE API
If you just need to wrap or unwrap the html from your inner component in different situations, then you can just keep the html in the component unwrapped, and wrap it only when needed by using the syntax:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner', decorationTagName='div', cssClassName='someClassName'}"></div>
If you need more complex logic, and you need to pass a value to your inner component template, you can use the selectors. The syntax for including the resource with selectors is:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner', selectors='mySelectorName'}"></div>
The syntax to check the selectors in the inner component is:
${'mySelectorName' in request.requestPathInfo.selectorString}"
or
${'mySelectorName' == request.requestPathInfo.selectorString}"