Programmatically adding handlers to react form handler - javascript

In trying to cut down on boilerplate code I'm trying to add handlers for common form fields to the handler object that will highlight the bad field, show some error, etc. So far I have this:
//FieldHelper.js
exports.withStandardStoreHandlers = function (storeObj, fields)
{
for(var i = 0; i < fields.length; i++)
{
var f = fields[i];
var midName = f.charAt(0).toUpperCase() + f.slice(1);
storeObj.prototype["onUpdate" + midName] = function(event) {
this[""+f] = event.target.value;
this[f+"ValidationState"] = '';
this[f+"HelpBlock"] = '';
};
}
return storeObj;
}
and then eventually I create an Alt store, that is exported thusly:
export default alt.createStore(FieldHelper.withStandardStoreHandlers(AddressStore, "firstName", "lastName", "street"));
And it actually does add the "onUpdateFirstName", "onUpdateLastName", etc. methods, but unfortunately they all use the field that was passed last. So in this example, I type some stuff into first name, but onUpdateFirstName modifies the street text field and its validation state/help block.
Clearly, the solution is to somehow make a copy of the "f" and "midName" variables so each of the dynamically created onUpdateX methods would have it's own value for them, instead of using the last available, but I just can't figure out how to do this in JavaScript. Preferably in as backwards-compatible way as possible, as this is going to be on the front end.

This is due to var in JavaScript being function scoped rather than block scoped as your code is expecting. A better explanation can be found here: https://stackoverflow.com/a/750506/368697
One solution is to make sure your f is declared inside a function scope on each iteration by using forEach:
//FieldHelper.js
exports.withStandardStoreHandlers = function (storeObj, fields)
{
fields.forEach(function (f) {
var midName = f.charAt(0).toUpperCase() + f.slice(1);
storeObj.prototype["onUpdate" + midName] = function(event) {
this[""+f] = event.target.value;
this[f+"ValidationState"] = '';
this[f+"HelpBlock"] = '';
};
});
return storeObj;
}

Related

Functions declared within loops referencing an outer scoped variable may lead to confusing

This is a jshint warning question.How can I solve this problem?
var comment_btn=document.querySelector('.comment_button');
var comment_ul=document.querySelector('.comment_ul');
var comment_text=document.querySelector('#comment');
comment_btn.onclick = function(){
var comment_li = document.createElement('li');
comment_li.className = 'comment_li';
if(comment_text.value != '') {
comment_li.innerHTML = comment_text.value + "<a class='comment_a' href='javascript:;'>Delete</a>";
comment_ul.insertBefore(comment_li,comment_ul.children[0]);
var del = document.querySelectorAll('.comment_a');
for (var i = 0; i < del.length; i++) {
del[i].onclick = function() {
comment_ul.removeChild(this.parentNode);
};
}
}
else {
alert('Please input!');
}
};
Warning:
Functions declared within loops referencing an outer scoped variable may lead to confusing semantics. (comment_ul) (W083)jshint(W083)
I really can't think of a solution,please help me.
Every time you add a new li, you are selecting EVERY delete anchor and adding another click event to it.
You should not be looping at all. You should just be selecting the anchor in the li you create and attach the event to that.
const comment_ul = document.querySelector("ul");
const comment_text = document.querySelector("#comment_text");
comment_btn.addEventListener('click', function() {
if (!comment_text.value.length) {
alert('Please input!');
return;
}
var comment_li = document.createElement('li');
comment_li.className = 'comment_li';
comment_li.innerHTML = comment_text.value + "<a class='comment_a' href='javascript:;'>Delete</a>";
comment_ul.insertBefore(comment_li, comment_ul.children[0]);
var del = comment_li.querySelector('.comment_a');
del.addEventListener('click', function(evt) {
evt.preventDefault();
comment_ul.removeChild(this.parentNode);
});
});
<ul></ul>
<input type="text" id="comment_text" />
<button id="comment_btn">Add</button>
The function you assign to onclick inside the loop doesn't care about any value that changes during the course of the loop. It only uses comment_url (defined before the loop) and this.
Create the function before the loop and assign it to a variable.
Copy it to onclick inside the loop.
This is a warning which will appear if you have a loop and you have a variable declared with var, which can sometimes cause problems. Assuming you aren't reassigning comment_ul anywhere, the problems aren't relevant to your situation, so all you need to do is make the linter happy.
One approach would be to declare the variable with const instead (best to declare variables with const when possible, and let when not).
const comment_ul = document.querySelector('.comment_ul');
Another would be to use forEach instead of for(....
That said, your code looks buggy at the moment - you're adding an event listener to every button every time comment_btn is clicked. Instead of doing that, consider adding only a single listener, ever - use event delegation. If the clicked element or one of its ancestors is a .comment_a, remove its parent.
commentContainer.addEventListener('click', (event) => {
const a = event.target.closest('.comment_a');
if (a) {
a.parentElement.remove();
}
});

Create a Reusable HTML Control w/ Javascript

So, I've been searching through some existing questions dealing with re-usable items in HTML and Javascript, and I'm not sure if there's anything that gives me the start I'm looking for. I'm not super well-versed in js, but rather than re-write the same code over and over again and have to perform the upkeep on it, I'd prefer to build a re-usable framework that I can apply in several places.
The basic layout is this: There's an input field with an "Add" button, each time you add a name, it displays below the input with a checkbox. When you uncheck it, the name is removed from the list.
I'm fine with styling and building the HTML, what I'm lost on is developing an object in js that I can apply in multiple places. What I had in mind was this:
function createInputControl(targetElementId) {
var newInputControl = new ItemInputControl();
newInputControl.onItemAdded = customItemAddedCallback;
newInputControl.onItemRemoved = customItemRemovedCallback;
newInputControl.createInElement(targetElementId);
}
That's the start I'm looking for. An object that I can create that has designated callbacks for when an item is added or removed via user interaction, and a way for me to draw it within an existing element on my page.
EDIT: What I'm looking for here is a skeleton of a javascript object (named ItemInputControl above) with these functions / properties that I can re-use throughout my site.
Ok, so If I understand you correctly - you're looking for help on how to make a globally accessible variable that can be used in your entire application, like jQuery. You have two main options for what you are looking to do
First - you could use an Object Literal, which exposes a single global variable and all of your methods are contained within:
(function (window) {
var inputControl = {
onItemAdded: function () {
// do stuff
},
onItemRemoved: function () {
// do stuff
},
createInElement: function (targetElementId) {
// do stuff
}
};
window.ItemInputControl = inputControl;
})(window);
This is used like so:
ItemInputControl.createInElement("elementId");
Your second option is to use Prototype:
(function (window) {
var inputControl = function () {
// Constructor logic
return this;
};
inputControl.prototype.onItemAdded = function () {
// do stuff
return this;
};
inputControl.prototype.onItemRemoved = function () {
// do stuff
return this;
};
inputControl.prototype.createInElement = function (elementId) {
// do stuff
return this;
};
window.ItemInputControl = inputControl;
})(window);
This would be used like so:
var newInputControl = new ItemInputControl();
newInputControl.createInElement("elementId");
For most cases in individual applications - I prefer to use Object Literals for my framework. If I were building a widely distributed javascript framework, I would probably use a prototype pattern. You can read more on prototype patters here: http://www.htmlgoodies.com/beyond/javascript/some-javascript-object-prototyping-patterns.html
Well, I'm not sure if this is exactly helpful, but perhaps it will contain a few ideas for you.
The two HTML elements needed are stored as format strings, and everything is dynamically added/removed in the DOM.
var listid = 0;
$(document).ready(function() {
var controlHtml = + // {0} = mainid
'<div>' +
'<input id="text-{0}" type="text" />' +
'<div id="add-{0}" class="addButton">Add</div>' +
'</div>' +
'<div id="list-{0}"></div>';
var listHtml = + // {0} = mainid, {1} = listid, {2} = suplied name
'<div id="list-{0}-{1}"><input id="checkbox-{0}-{1}" type="checkbox class="checkboxClass" checked />{2}<div>';
$('#content').append(controlHtml.f('0'));
$('.addButton').click(function(e) { addClick(e); });
});
function addClick(e) {
var id = e.currentTarget.id.split('-')[1];
var name = $('text-' + id).val();
$('.list-' + id).append(listHtml.f(id, listid, name));
listid++;
$('.checkboxClass').click(function() { checkboxClick(e); });
}
function checkboxClick(e) {
$('#' + e.currentTarget.id).remove();
}
String.prototype.f = function () { var args = arguments; return this.replace(/\{(\d+)\}/g, function (m, n) { return args[n]; }); };
And of course very minimal HTML to allow a hook for adding your control:
<body>
<div id="content"></div>
</body>

Dynamically Modifying Value of an Existing Event Handler

Quick background: I'm updating existing code to separate event handlers from html objects and, in an onload function I'm then assigning all necessary handlers.
$('input[name^="interactiveElement_"]').each(function() {
var fieldName = "interactiveElement_";
var elementNumber = $(this).attr("name").substring(fieldName.length,$(this).attr("name").indexOf("_",fieldName.length));
var subElementNumber = $(this).attr("name").substring((fieldName+itemNumber+'_').length,$(this).attr("name").lastIndexOf('_'));
var rowNumber = $(this).attr("name").substring($(this).attr("name").lastIndexOf('_')+1);
$(this).on("click.custNamespace", function() {
updateThisElementsMetadata(elementNumber, subElementNumber, rowNumber);
updatePreview(elementNumber);
});
});
Now for the hard part. In this interface, users will be able to trigger clones of existing elements. These clones need to have some of their handler arguments updated to new values.
Before separating out the events, I was doing that with this:
var regex = /([^0-9]+[0-9]+[^0-9a-zA-Z]+[0-9]+[^0-9a-zA-Z]+)([0-9]+)([^0-9]+)?/g;
$(element).attr("onclick",
$(element)
.attr("onclick")
.replace(regex, "$1"+newValue+"$3"));
and so on for each possible event these elements could have.
But, now using jQuery's on(event, handler) I no longer have visibility to what the handler is.
I've tried this (following this question - Get value of current event handler using jQuery):
jQuery._data(element).events.click[0].handler
But, this returns the function with variable names not values.
function () {
updateThisElementsMetadata(elementNumber, subElementNumber, rowNumber);
updatePreview(elementNumber);
}
Where I would have hoped for:
function () {
updateThisElementsMetadata(1, 2, 1);
updatePreview(1);
}
Looking in the console log I see that jQuery._data(element).events.click[0] has the values in handler => < function scope > => Closure but it doesn't seem like there is dot notation to access that, or even an array that I can dynamically cycle through.
If you tell me this isn't possible, I could change all the functions' args to just be $(this) and parse out the necessary values from it in each function, or I guess I could have a helper function... but if I could keep a similar setup to what was there it would ease other dev's learning curve.
Final Solution
To reduce duplicate code, I created a Javascript function/object that parses out necessary info from name/id tag (instead of data- attributes to reduce redundant info). Whenever an event handler is triggered it will first parse out the necessary values and then run the function w/ them.
$('input[name^="interactiveElement_"]').on("click.custNamespace", function() {
var inputField = inputFieldClass(this);
updateThisElementsMetadata(inputField.elementNumber, inputField.subElementNumber, inputField.rowNumber);
updatePreview(inputField.elementNumber);
});
var inputFieldClass = function(field) {
var fieldIdentity = $(field).attr("name") === undefined ? $(field).attr("id") : $(field).attr("name");
var fieldName = fieldIdentity.substring(0,fieldIdentity.indexOf("_")),
elementNumber = fieldIdentity.substring(fieldName.length + 1,fieldIdentity.indexOf("_",fieldName.length + 1)),
subElementNumber = fieldIdentity.substring((fieldName+'_'+elementNumber+'_').length,fieldIdentity.lastIndexOf('_')),
rowNumber = fieldIdentity.substring(fieldIdentity.lastIndexOf('_')+1);
return {
fieldName : fieldName,
elementNumber : elementNumber,
subElementNumber : subElementNumber,
rowNumber : rowNumber,
getInputName : function () {
return this.name + "_" + this.elementNumber + "_" + this.subElementNumber + "_" + this.rowNumber;
}
};
};
What I was suggesting in the comments was something like this:
$('input[name^="interactiveElement_"]').on("click.custNamespace", function() {
var fieldName = "interactiveElement_";
var elementNumber = $(this).attr("name").substring(fieldName.length,$(this).attr("name").indexOf("_",fieldName.length));
var subElementNumber = $(this).attr("name").substring((fieldName+itemNumber+'_').length,$(this).attr("name").lastIndexOf('_'));
var rowNumber = $(this).attr("name").substring($(this).attr("name").lastIndexOf('_')+1);
updateThisElementsMetadata(elementNumber, subElementNumber, rowNumber);
updatePreview(elementNumber);
});
Also, instead of parsing everything from the element's name, you could use data- attributes to make it cleaner (e.g. data-element-number="1", etc.).

Assign a Function Argument with a Loop

I have an array of list items in a piece of Javascript code. I would like to assign an onclick event handler to each one. Each handler would be the same function, but with a different input argument. Right now I have:
function contentfill(i) {
box = document.getElementById("text");
box.style.background="rgba(0,0,0,0.8)";
var content = new Array();
contentdivs = document.querySelectorAll("#contentfill>div");
box.innerHTML = contentdivs[i].innerHTML;
}
li[3].onclick = function() {contentfill(0);};
li[4].onclick = function() {contentfill(1);};
li[5].onclick = function() {contentfill(2);};
This works well enough, but I would like to achieve the same thing with a loop, for example:
for(i=3;i<=5;i++) {
j=i-3;
li[i].onclick = function() {contentfill(j);};
}
This, however, does not work. Since j seems to be defined as 2 at the end of the loop, each time I click, it only seems to call contentfill(2).
For an alternative approach, consider having each of the elements aware of what argument it should be using.
for (var i = 0; i < 3; i++) {
var el = li[i + 3];
el.dataset.contentIndex = i;
el.addEventListener('click', contentfill);
}
Then contentfill would have to extract the argument from .dataset instead of taking an argument, of course. (This is the same mechanism as jQuery's $.data.)
I tend to prefer this since (a) it doesn't generate tons of tiny wrappers, (b) it allows me to later examine and possibly change the "arguments", and (c) it lets me predefine them in the document using data- attributes. Effectively changes them from function arguments into behavior.
The value of i - 3 should be bound to the click handler function; a closure can provide this functionality:
li[i].onclick = (function(j) {
return function() {
contentfill(j);
}
)(i - 3));
Btw, it's better practice to use addEventListener or attachEvent to register click handlers.

Single onchange function for multiple controls in java script

I am new to java script and having a problem.
I am creating a form dynamically from Json string what we obtain some how ( not relevant ) - Keeping all those controls information in Array (allParamArray in code) . A control can have parent , hence a parent control can have dependents ( children ) - I want to attach onchange event to each control which have dependents . The dependents are comma separated and taken care of in the refreshDependents function.
<code>
for (mp = 0; mp < allParamsArray.length; mp++) {
if (allParamsArray[mp].dependents) {
var parent = allParamsArray[mp].name;
var dependents = allParamsArray[mp].dependents;
document.getElementById(parent).onchange = function() {
refreshDependents(parent, dependents, this.selectedIndex)
};
}
}
</code>
The Problem is , when I change value on form for a control refreshDependents has the last parent information and not of the control which I change.
I was wondering is there any way we can achieve multiple control pointing to same onchange function and in onchange function we get the correct information about the control which is being changed ?
This should do it:
function makeHandler(parent, dependents, selectedIndex){
return function(){
refreshDependents(parent, dependents, selectedIndex);
};
}
for (mp = 0; mp < allParamsArray.length; mp++) {
if (allParamsArray[mp].dependents) {
var parent = allParamsArray[mp].name;
var dependents = allParamsArray[mp].dependents;
document.getElementById(parent).onchange = makeHandler(parent, dependents, this.selectedIndex);
}
}
The makeHandler function returns a new function object, which will keep the values that the parent, dependents, and selectedIndex variables had at the time it was created.

Categories

Resources