Javascript binding Higher-order function with input onchange not working - javascript

My goal is to call a function with a string argument, calling function will return a function which will receive the event object from html input object, and I want to use the string argument in the second function.
const person = {
name:'',
age:''
};
const regForm = (field) => {
console.log('field : ', field);
return event => {
person[field]=event.target.value;
document.getElementById("demo").innerHTML = JSON.stringify(person);
}
};
<input onchange="regForm('name')"/>
<input onchange="regForm('age')"/>
<p id="demo"></p>

The issue is your onchange attribute currently has a string-based value and it is effectively evald when the change event is fired. As #PeterSeliger comments, regForm simply returns a function.
regForm could return anything, and so the default change handler makes no assumptions about your answer. You maybe be expecting that the returned function would be called with the event, but instead the default handler simply discards the value.
One solution would be to use JavaScript's onchange property, instead of HTML's onchange attribute -
const person = {
name:'',
age:''
}
const regForm = field => event =>
person[field] = event.target.value
const form =
document.forms.sample
form.name.onchange = regForm("name")
form.age.onchange = regForm("age")
form.onsubmit = event => {
event.preventDefault()
console.log("Submitted", JSON.stringify(person))
}
<form id="sample">
<input name="name" placeholder="enter your name" />
<input name="age" placeholder="enter your age" />
<input type="submit" />
</form>
And since you are familiar with event delegation, ie event.target, you can remove additional duplication. Looks like regForm just kinda disappeared! -
const data =
{}
const form =
document.forms.sample
form.onchange = event =>
data[event.target.name] = event.target.value
form.onsubmit = event => {
event.preventDefault()
console.log("Submitted", JSON.stringify(data))
}
<form id="sample">
<input name="name" placeholder="enter your name"><br>
<input name="age" placeholder="enter your age"><br>
<input name="foo" placeholder="enter your foo"><br>
<input name="bar" placeholder="enter your bar"><br>
<input type="submit" />
</form>
Output
Submitted {"name":"1","age":"2","foo":"3","bar":"4"}
Functions that take other functions as input and/or return other functions as output are called higher-order functions. There are a variety of terminologies and techniques for dealing with them. For related reading, see What do multiple arrow functions mean?
const preventDefault = f => event =>
( event.preventDefault()
, f(event)
)
const logKeypress = event =>
console.log(event.which)
document
.querySelector('input[name=foo]')
.addEventListener('keydown', preventDefault(logKeypress))
<input name="foo" placeholder="type here to see ascii codes" size="50">

In oder to come close to what the OP might have wished to achieve, one should break down the code into the state-change handling task(, maybe a render task) and the listener-initializing task ...
const person = {
name:'',
age:''
};
function renderPersonStateChange(personReference, key, value) {
personReference[key] = value;
document.body.querySelector('#demo').textContent = JSON.stringify(personReference);
}
function handleTextInputStateChange(evt) {
const elm = evt.currentTarget;
const key = elm.name;
renderPersonStateChange(person, key, elm.value);
}
// initialize event listeners
document.body.querySelectorAll('input').forEach(elm =>
elm.addEventListener('change', handleTextInputStateChange, false)
);
<input name='name' placeholder="enter person's name"/>
<input name='age' placeholder="enter person's age"/>
<p>
<code>
<pre id="demo">
</pre>
</code>
</p>
If one wishes tobind the reference that's state should be changed, the above code then slightly alters (only for the change-handler and the event-initialization parts) towards ...
const person = {
name:'',
age:''
};
function renderPersonStateChange(personReference, key, value) {
personReference[key] = value;
document.body.querySelector('#demo').textContent = JSON.stringify(personReference);
}
function handleStateChangeForBoundPerson(evt) {
const personReference = this;
const elm = evt.currentTarget;
const key = elm.name;
renderPersonStateChange(personReference, key, elm.value);
}
// initialize event listeners
document.body.querySelectorAll('input').forEach(elm =>
elm.addEventListener('change', handleStateChangeForBoundPerson.bind(person), false)
);
<input name='name' placeholder="enter person's name"/>
<input name='age' placeholder="enter person's age"/>
<p>
<code>
<pre id="demo">
</pre>
</code>
</p>

Related

Collect URL.pathname from user input and append to existing url

I'm trying to Collect URL.pathname from user input and add to existing url, but when I do so, it doesn't seem to work when I output new URL.
<form id="myForm">
<input id="inputId" type="search" placeholder="Enter BSC Contract Address...">
<button onclick="getEndString()" id="submit" type="submit">Go</button>
</form>
let apiUrl = new URL('https://api.pancakeswap.info');
let myInput = document.getElementById("inputId");
let myForm = document.getElementById("myForm");
apiUrl.pathname = `/api/v2/tokens/` + getEndString(myInput.value);
function getEndString(x) {
return x;
}
myForm.addEventListener("submit", (e) => {
e.preventDefault();
console.log(apiUrl.toString());
});
Remove the onclick listener from the <button> element.
<form id="myForm">
<input id="inputId" type="search" placeholder="Enter BSC Contract Address...">
<button id="submit" type="submit">Go</button>
</form>
Move the assignement of the pathname into the event handler. The reason for this is that you want to read the value of the input and create the value for the pathname whenever you submit. Doing it outside of the event handler will only trigger the assignment whenever the script is loaded, but not after that.
let apiUrl = new URL('https://api.pancakeswap.info');
let myInput = document.getElementById("inputId");
let myForm = document.getElementById("myForm");
myForm.addEventListener("submit", (e) => {
e.preventDefault();
apiUrl.pathname = `/api/v2/tokens/` + myInput.value;
console.log(apiUrl.toString());
});

In JS there is any Pattern or Class style to follow when primarily manipulating DOM Elements?

I'm trying to improve and clean a bit my code that often have this bunch of "querySelectors" and global variables, so I want to know, there is any pattern or use of Classes in this cases? or just break it in a lot of functions and separate the files is the 'right' thing to do?
this code is just a example of what I mean by "bunch of querySelectors"
//variables
const form = document.querySelector("#form_contrato");
const btnRespAluno = document.querySelector("#label_checkbox_resp_aluno");
const btnCombo = document.querySelector("#label_check_combo");
const checkCombo = document.querySelector("#check_combo");
const cursoSelect = document.querySelector("#curso_nome");
const select = document.getElementById("curso_nome");
const comboCurso1 = document.querySelector("#combo_curso_1");
const comboCurso2 = document.querySelector("#combo_curso_2");
const valor = document.querySelector("#curso_valor");
const desconto = document.querySelector("#curso_desconto");
const total = document.querySelector("#curso_total");
const comboTextarea = document.querySelector("#combo_textarea");
const modulos = document.querySelector("#curso_modulos");
const duracao = document.querySelector("#curso_duracao");
const parcelas = document.querySelector("#curso_parcelas");
const vencimento = document.querySelector("#curso_vencimento");
const fieldsetAluno = document.querySelector("#fieldset_aluno");
const loadinContrato = document.querySelector("#loading_contrato");
//Listeners
form.addEventListener("submit", sendForm);
btnRespAluno.addEventListener("input", checkboxRespAluno);
btnCombo.addEventListener("input", inputComboCheckbox);
cursoSelect.addEventListener("change", insertCursoInfo);
comboCurso2.addEventListener("input", insertComboTextarea);
valor.addEventListener("input", insertInputValorTotal);
desconto.addEventListener("input", insertInputValorTotal);
desconto.addEventListener("change", insertComboTextarea);
//Masks
VMasker(document.querySelector("#curso_valor")).maskMoney();
VMasker(document.querySelector("#curso_desconto")).maskMoney();
VMasker(document.querySelector("#resp_cep")).maskPattern("99999-999");
VMasker(document.querySelector("#resp_cpf")).maskPattern("999.999.999-99");
VMasker(document.querySelector("#resp_rg")).maskPattern("99.999.999-S");
VMasker(document.querySelector("#aluno_rg")).maskPattern("99.999.999-S");
VMasker(document.querySelector("#aluno_cep")).maskPattern("99999-999");
function insertInputValue(value, target) {
document.querySelector(target).value = value;
}
function insertInputDateValue(...) {
...
}
function replaceToNonBreakSpaceHifen(...) {
...
}
//and alot of other functions and function calls bellow....
Yes, breaking it into modules (or functions/IIFEs) for separate functionalities would be the way to go. You don't need any class syntax because you're not creating objects anywhere, and it seems your listeners don't share any state either.
So watch out only for variables that are reused (used in multiple places). For everything else, put the variable, the addEventListener call, and the listener declaration together. You might even avoid single-use temporary variables and just write document.querySelector("…").addEventListener("…", function(event) { … }); as a single statement.
If you need them, you need them, but it does look unwieldy. It really depends on your use case. Elements-as-variables can sometimes be accessed dynamically. For example, here's a form with a bunch of values, names, ids and types. Without setting up variables, we can loop through and get their values, tagNames, etc.
document.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
[...e.target.querySelectorAll('input, datalist, select, textarea')].forEach(el => {
let val = el.value;
if (['select', 'datalist'].includes(el.tagName.toLowerCase())) val = el.querySelector('option[selected]').value;
console.log('tagname: ', el.tagName, 'name: ', el.name, 'id: ', el.id, 'value: ', val);
})
})
label{
display:block;
margin-bottom:5px;
}
<form>
<label>Name <input type="text" name="customer_name" value="test value" required>
</label>
<label>Phone <input type="tel" value="123-123-1234" name="phone_number">
</label>
<label>Email <input type="email" name="email_address" value="test#value.com" >
</label>
<label>Pickup Date/Time <input type="date" value="2021-10-02" name="pickup_time" required>
</label>
<label>Pickup Place <select id="pickup_place" name="pickup_place">
<option value="" selected="selected">Select One</option><option value="office" selected>Taxi Office</option><option value="town_hall" >Town Hall</option><option value="telepathy" >We'll Guess!</option>
</select>
</label>
<label>Dropoff Place <input type="text" name="dropoff_place" value="test value" required list="destinations">
</label>
<datalist id="destinations">
<option value="Airport"><option value="Beach">
<option selected value="Fred Flinstone's House">
</datalist>
<label>Special Instructions
<textarea name="comments" maxlength="500">Bring chocolate cake</textarea>
</label>
<button>Submit Booking</button>
</form>
Event listener functions can sometimes be consolidated, testing for the listener event.type and the target event.target. Like the logic for mouseover and mouseout can be combined, like this
const onMO = (e) => {
console.log('Listener fired for:', e.type, 'on', e.target.tagName, 'with class name', e.target.getAttribute('class'));
}
let m = document.querySelector('.mo');
m.addEventListener('mouseover', onMO);
m.addEventListener('mouseout', onMO)
.mo {
padding: 20px;
text-align: center;
background: #f0f0f0;
}
<div class='mo'>Mouse in and out</div>

how to button on click takes all the content from textarea, runs a function and then displays the final value inside new div?

I'm in the learning stages, can you please give me the answer with the explanation.
Also if possible, I would appreciate it if you could explain to me what I have done wrong here and then also show me the better way of doing it.
//ARROW FUNCTION
const reverseNumber = num => (num.toString().split("").reverse().join(""));
let textAreaEl = document.querySelector('#text-area');
let submitButtonEl = document.querySelector('#output-button');
let outputAreaEl = document.querySelector('#output');
submitButtonEl.addEventListener("click", () => {
textAreaValue = reverseNumber(textAreaEl)
outputAreaEl.innerHTML = textAreaValue.value;
});
<form action="">
<label for="object" title="object">JavaScript function that returns a passed string with letters in alphabetical order</label>
<textarea id="text-area" name="object-name" id="" cols="30" rows="5"></textarea>
<input type="button" value="RETURN" id="return-value" />
<div id="output"></div>
</form>
First of all, there is no element with #output-button. You need to fix that.
You are passing the element itself to the reverseNumber(), you should pass the value to function.
I will also suggest you use innerText or textContent instead of innerHTML if the text is plain text (not htmlString).
Demo:
const reverseNumber = num => (num.toString().split("").reverse().join(""));
let textAreaEl = document.querySelector('#text-area');
let submitButtonEl = document.querySelector('#output-button');
let outputAreaEl = document.querySelector('#output');
submitButtonEl.addEventListener("click", () => {
textAreaValue = reverseNumber(textAreaEl.value); //pass value here
outputAreaEl.textContent = textAreaValue; //no need to use value here
});
<form action="">
<label for="object" title="object">JavaScript function that returns a passed string with letters in alphabetical order</label>
<textarea id="text-area" name="object-name" id="" cols="30" rows="5"></textarea>
<input type="button" value="RETURN" id="output-button"/> <!--fix the id here-->
<div id="output"></div>
</form>
Your script should be like this
const reverseNumber = num => (num.toString().split("").reverse().join(""));
console.log(reverseNumber(54321));
let textAreaEl = document.querySelector('#text-area');
let submitButtonEl = document.querySelector('#return-value');
let outputAreaEl = document.querySelector('#output');
submitButtonEl.addEventListener("click", () => {
let textAreaValue = reverseNumber(textAreaEl.value);
outputAreaEl.innerHTML = textAreaValue;
});
First, your document.querySelector('#output-button') is not match with <input type="button" value="RETURN" id="return-value"/>.
Second, you have to use variable declaration keyword to get textAreaEl.value
First of all textarea id is incorrect so querySelector is returning undefined and click event is not attached. I have corrected the button id in html.
You need to use textAreaEl.value to find the textarea text and pass it to reverseNumber function.
//ARROW FUNCTION
const reverseNumber = num => (num.toString().split("").reverse().join(""));
let textAreaEl = document.querySelector('#text-area');
let submitButtonEl = document.querySelector('#output-button');
let outputAreaEl = document.querySelector('#output');
submitButtonEl.addEventListener("click", () => {
textAreaValue = reverseNumber(textAreaEl.value)
outputAreaEl.innerHTML=textAreaValue;
});
<form action="">
<label for="object" title="object">JavaScript function that reverses a number</label>
<textarea id="text-area" name="object-name" id="" cols="30" rows="5"></textarea>
<input type="button" value="RETURN" id="output-button"/>
<div id="output"></div>
</form>

JS Form: How do I push() into an array, without creating a new array every time I click Submit?

Every time I click Submit, its creating a new array with an object
Array should receive keep receiving objects and not create a new one
JS:
const form = document.querySelector('#form')
form.addEventListener('submit', (e) => {
e.preventDefault()
const title = document.querySelector('#title').value
const img = document.querySelector('#img').value
const story = document.querySelector('#story').value
const author = document.querySelector('#author').value
const eachStory = {
myTitle : title,
myImg : img,
myStory : story,
myAuthor : author
}
let stories = []
stories.push(eachStory)
stories.forEach((story) => {
root.innerHTML +=
`
${eachStory.myTitle}
${eachStory.myStory}
${eachStory.myImg}
${eachStory.myAuthor}
`
})
console.log(stories)
})
HTML:
<body>
<div>
<form id="form">
<input type="text" id="title" >
<input type="text" id="img" >
<input type="text" id="story" >
<input type="text" id="author" >
<button>SUBMIT</button>
</form>
<div id="root"></div>
</div>
Can someone tell me what I should do here?
I need to add objects to the same array every time i click submit
Everytime the form is submitted, the submit event fires up and the handler function is executed. Since, you are initializing a new stories array inside your function, every time the form is submitted, a new stories array is created.
You might want to move your stories array declaration out of the function, so that new posts are added to the existing the stories array.
const form = document.querySelector('#form')
let stories= [];
form.addEventListener('submit', (e) => {...}
First declare the array outside the submit handler. Secondly if you want to append it to the dom you can avoid the array and iterating it. Also iterating over the array may create duplication over row
const form = document.querySelector('#form')
let stories = []
form.addEventListener('submit', (e) => {
e.preventDefault()
const title = document.querySelector('#title').value;
const img = document.querySelector('#img').value;
const story = document.querySelector('#story').value;
const author = document.querySelector('#author').value;
root.innerHTML +=
`<div>
${title}
${img}
${story}
${author}</div>
`;
stories.push({
myTitle: title,
myImg: img,
myStory: story,
myAuthor: author
})
})
<div>
<form id="form">
<input type="text" id="title">
<input type="text" id="img">
<input type="text" id="story">
<input type="text" id="author">
<button>SUBMIT</button>
</form>
<div id="root"></div>
</div>
On Submit click it will create new Object and add in array.And next/every click it will add only object in existing array and not will remove the existing Object.
const form = document.querySelector("#form");
let stories = [];
form.addEventListener("submit", e => {
e.preventDefault();
const title = document.querySelector("#title").value;
const img = document.querySelector("#img").value;
const story = document.querySelector("#story").value;
const author = document.querySelector("#author").value;
var storyObj = {};
storyObj["myTitle"] = title;
storyObj["myImg"] = img;
storyObj["myStory"] = story;
storyObj["myAuthor"] = author;
stories.push(storyObj);
stories.forEach(story => {
root.innerHTML += `
${story.myTitle}
${story.myStory}
${story.myImg}
${story.myAuthor}
`;
});
console.log("Create data value==>+", JSON.stringify(stories));
});

Serialize HTML form to JSON with pure JavaScript

I have seen this method of serializing a form to JSON and it's working fine. My question is: How can I achieve this with pure JavaScript, without using any jQuery code? I am sorry if the question is dumb, but I'm still learning so if anyone can help me, I'll be grateful.
(function ($) {
$.fn.serializeFormJSON = function () {
var objects = {};
var anArray = this.serializeArray();
$.each(anArray, function () {
if (objects[this.name]) {
if (!objects[this.name].push) {
objects[this.name] = [objects[this.name]];
}
objects[this.name].push(this.value || '');
} else {
objects[this.name] = this.value || '';
}
});
return objects;
};
})(jQuery);
$('form').submit(function (e) {
e.preventDefault();
var data = $(this).serializeFormJSON();
console.log(data);
/* Object
email: "value"
name: "value"
password: "value"
*/
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form action="#" method="post">
<div>
<label for="name">Name</label>
<input type="text" name="name" id="name" />
</div>
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" />
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password" />
</div>
<p>
<input type="submit" value="Send" />
</p>
</form>
P.S.
Also in jQuery is this the right way to send multiple JSON objects from user input as One String, because I am searching for a way to do that?
You can try something like this:
function formToJson(){
var formElement = document.getElementsByTagName("form")[0],
inputElements = formElement.getElementsByTagName("input"),
jsonObject = {};
for(var i = 0; i < inputElements.length; i++){
var inputElement = inputElements[i];
jsonObject[inputElement.name] = inputElement.value;
}
return JSON.stringify(jsonObject);
}
This solution works only if you have a single form on the page, to make it more general the function could e.g. take the form element as an argument.
You can use Array.reduce, something like
// get array of all fields and/or selects (except the button)
const getFields = () => Array.from(document.querySelectorAll("input, select"))
.filter(field => field.type.toLowerCase() !== "button");
// get id, name or create random id from field properties
const getKey = field => field.name
|| field.id
|| `unknown-${Math.floor(1000 * Math.random()).toString(16)}`;
// get data, simple object
const getFormData = () => getFields()
.reduce( (f2o, field) => ({...f2o, [getKey(field)]: field.value}), {} );
// log the result
const logResult = txt => document.querySelector("#result").textContent = txt;
// get data, array of field objects
const getMoreFormData = () => getFields()
.reduce( (f2o, field) =>
f2o.concat({
id: field.id || "no id",
name: field.name || "no name",
idGenerated: getKey(field),
type: field.type,
value: field.value }
),
[] );
// handling for buttons
document.addEventListener("click", evt => {
if (evt.target.nodeName.toLowerCase() === "button") {
console.clear();
logResult(/simple/.test(evt.target.textContent)
? JSON.stringify(getFormData(), null, " ")
: JSON.stringify(getMoreFormData(), null, " ")
);
}
} );
<form action="#" method="post">
<div>
<label for="name">Name</label>
<input type="text" name="name" id="name" value="Pete"/>
</div>
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" value="pete#here.com"/>
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password" />
</div>
<div>
<label>Field without name or id</label>
<input type="number" value="12345" />
</div>
</form>
<p>
<button>Data simple object</button> <button>Data fields array</button>
</p>
<pre id="result"></pre>
Remember that for checkboxes, value attribute can be either on or off string. This is unwanted. Here is my solution, based on this codepen.
let json = Array.from(form.querySelectorAll('input, select, textarea'))
.filter(element => element.name)
.reduce((json, element) => {
json[element.name] = element.type === 'checkbox' ? element.checked : element.value;
return json;
}, {});
OR
let json = {};
Array.from(form.querySelectorAll('input, select, textarea'))
.filter(element => element.name)
.forEach(element => {
json[element.name] = element.type === 'checkbox' ? element.checked : element.value;
});
OR (with typescript)
export type FormJson = {[key: string]: boolean | string};
export const toJson = (form: HTMLFormElement): FormJson =>
Array.from(form.querySelectorAll<HTMLFormElement>('input, select, textarea'))
.filter(element => element.name)
.reduce<FormJson>((json, element) => {
json[element.name] = element.type === 'checkbox' ? element.checked : element.value;
return json;
}, {});
To serialize your form you can do this (note that I added an onsubmit in the form tag):
HTML and JavaScript:
function serializeForm(e) {
e.preventDefault(); // prevent the page to reload
let form = e.target; // get the form itself
let data = new FormData(form); // serialize input names and values
let objSerializedForm = {}; // creating a new object
for (let [name, value] of data) { // iterating the FormData data
objSerializedForm[name] = value; // appending names and values to obj
}
console.log(objSerializedForm);
}
<form action="#" method="post" onsubmit="serializeForm(event)">
<div>
<label for="name">Name</label>
<input type="text" name="name" id="name" />
</div>
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" />
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password" />
</div>
<p>
<input type="submit" value="Send" />
</p>
</form>
Than you can do whatever you want with your objSerializedForm, getting each value by calling objSerializedForm.name.

Categories

Resources