Separate array of elements by attribute - javascript

Let's say I've got a form with several inputs, and I use a pretty standard jQuery statement to get them all:
var inputs = $("#some-form").find("input")
Now, I'd like to act on those inputs, but let's say I want to treat the radio button and/or checkbox groups as a single thing. How can I split inputs out into elements grouped by an attribute, say name. Note, that I don't know what the name is going to be when the processing starts.
In human terms, I need the logic do do something along the lines of:
Let me iterate over the list of inputs. For each input, let me check
to see if it's already been added to a placeholder array. If so,
leave it alone. If not, add it and everything with it's name to said
placeholder array (as a sub array).
Essentially, I'd like something like this:
[[<input type="text" name="name1">], [<input type="radio" name="name2">,<input type="radio" name="name2">]]

Try using attribute selector inside a filter.
var $formInput = $('#some-form').find('input');
var inputText = $formInput.filter('[type=text]')
var otherInput = $formInput.filter("[type=radio]")
.add($formInput.filter('[type=checkbox]'));
or even better
var otherInput = $formInput.filter(function () {
return this.type == 'radio' || this.type == 'checkbox';
});
DEMO: http://jsfiddle.net/utwaf/
How can I split inputs out into elements grouped by an attribute, say name
var elements = []; //elements by name
var $formInput = $('#some-form').find('input');
elements.push($formInput.filter(function() {
return this.name == 'name1';
});
elements.push($formInput.filter(function() {
return this.name == 'name2';
});
Note: All elements pushed into the array are jQuery objects.

function getInputsPartitioned(formEl) {
var inputEls = formEl.querySelectorAll("input");
var inputsByName = Object.create(null);
for (var i = 0; i < inputEls.length; ++i) {
var inputEl = inputEls[i];
var name = inputEl.name;
if (!(name in inputsByName)) {
inputsByName[name] = [];
}
inputsByName[name].push(inputEl);
}
// `inputsByName` now looks like this:
// {
// name1: [<input type="text" name="name1">],
// name2: [<input type="radio" name="name2">, <input type="radio" name="name2">]
// }
// To get it into arrays:
return Object.keys(inputsByName).map(function (name) {
return inputsByName[name];
});
}
Bonus: no jQuery needed.

Related

Making variables assigned HTML ids with a function

I can make variables one by one like this:
var bookName = document.getElementById('bookName').value,
author = document.getElementById('author').value,
translator = document.getElementById('translator').value,
pageCount = document.getElementById('pageCount').value,
publisher = document.getElementById('publisher').value,
isbn = document.getElementById('isbn').value,
printingYear = document.getElementById('printingYear').value;
But it's so hard to write and it doesn't fit with the DRY rule. So I changed the code to this:
function variableMaker(argument) {
var tags = document.getElementsByTagName(argument);
for (var i = 0; i < tags.length; i++) {
var tags[i].name = tags[i].value;
}
}
variableMaker(input);
But I can't understand if it is the true way or if it is working? How do I check if it's true or not?
In this code, I tried to get the computer find all the input tags and make variables with their name property and assign it to its values for each of them.
If I understand correctly then you want to gather data from all <input> elements. If so, then you need to call it like this:
variableMaker('input'); // use quotes!
Still even then your function does not return anything, it just ends.
You'd also better create your own object for the return collection, instead of adding values to an existing object.
Here is a working solution:
function variableMaker(tagName) {
var elements = document.getElementsByTagName(tagName);
var items = {};
for (var i = 0; i < elements.length; i++) {
var elem = elements[i];
items[elem.id] = elem.value; // Use id as key, each points to the corresponding value.
}
return items;
}
var values = variableMaker('input');
console.log(values); // show the entire return object
console.log(values.author); // access individual items from the return object
console.log(values.isbn);
<input type="text" id="author" value="Dahl">
<input type="text" id="isbn" value="1234">
.

Fill javascript object with form data

I have an object declared, and I have an html form with some matching fields.
All fields in the form are in the object, but the object also has a couple of additional variables and functions.
I'd like to fill the object with the data entered in the form, what I'm trying right now overwrites the declared object, and so doesn't have the functions nor the other variables.
The object :
var Container = {
nodes: [],
Contains: function (Node) {
for (var i = 0; i < this.nodes.length; i++) {
if (this.nodes[i].nodeID === Node.nodeID)
return (i);
}
return (-1);
}
How I fill it from the form :
const handleContainerForm = event => {
event.preventDefault();
ContainerFormToJSON(form.elements);
var i = JSONData.Contains(Container);
if (i === -1)
JSONData.containers.push(Container);
else
JSONData.container[i] = Container;
output = JSON.stringify(JSONData, null, " ");
displayContents(output);
};
The form has ID, Title, Folder, Image and Description as fields, so this last Container object doesn't have the Contains() function nor the nodes[] array.
How do I end up with a complete, filled version of the object I have declared ?
In ContainerFormToJSON function, before the statement
return Container
define:
//container.nodes and container.contains
You are right, JavaScript is very different from C#, especially in regards to OOP. But that doesn't make it better or worse.
In JavaScript, you don't need to declare an object's properties, like you have to when you use classes. I think that you only want to serialize the form's input values to JSON. I recommend not to use an object that additionally has a nodes property and a Contains method.
If you need to keep a copy of the unserialized object, create two objects:
class Container {
constructor () {
this.nodes = [];
}
indexOf (node) {
return this.nodes.findIndex(n => n.nodeID === node.nodeID);
}
}
Container.nodeID = 0; // think of it as a static property
function extractValues (elements) {
// 'elements' is an array of <input> elements
// the 'container' will be filled and serialized
var container = new Container();
for (var index in elements) {
var element = elements[index];
container[element.name] = element.value;
}
container.nodeID = Container.nodeID++; // give the container a unique ID
return container;
}
var inputs = document.querySelectorAll('input');
var jsonData = new Container();
document.querySelector('button').addEventListener('click', function () {
var newContainer = extractValues(inputs);
var index = jsonData.indexOf(newContainer);
if (index === -1) {
jsonData.nodes.push(newContainer);
} else {
jsonData.nodes[index] = newContainer;
}
var jsonString = JSON.stringify(jsonData, null, ' ');
console.log(jsonString);
});
<input name="containerID">
<input name="containerTitle">
<!-- ... -->
<button>Serialize</button>
Please note: only setting an object's properties doesn't make it to JSON. It's only JSON if it's serialized to a string. I recommend this article. To serialize a JavaScript object, use JSON.stringify.
Edit:
Looking at the edit of your question, I think it might be preferable to create a Container class. Both jsonData and the containers of the form data will be instances of that class. It can contain other containers (nodes), and can get the index of such a nested container using the indexOf method. I implemented this in the above code snippet. Whenever you hit the "Serialize" button, a new container with the current <input>s' contents will be added to jsonData. The JSON form of jsonData will be logged to the console.
I hope this is what you are looking for. To better understand JavaScript OOP,
take a look at some of the articles at MDN.

Renaming formelements in a particular range with jquery

I've multiple autogenerated forms on a page. They are named in a particular manner like:
form-0-weight, form-1-weight, form-2-weight etc.
<ul>
<li>
<input id="id_form-0-weight" type="text" name="form-0-weight">
<a class="deleteIngredient" href="">x</a>
</li>
<li>
....more forms
</li>
</ul>
The user can add and delete forms. If a form get's deleted, the remaining ones should be renamed to stay in order. e.g. "form-1-weight" gets deleted >> "form-2-weight" will be renamed to "form-1-weight".
The total number of forms is stored in a hidden field named TOTAL_FORMS.
I'm trying to achieve this with a simple for loop.
The problem is that all the forms after the deleted one get the same name.
e.g. If I delete form-2-weight, all the following forms get the name form-2-weight instead of 2, 3, 4 etc.
$(".deleteIngredient").click(function(e){
e.preventDefault();
var delete = $(this).closest('li');
name = delete.children('input').attr("name");
count = name.replace(prefix,'');
count = name.replace("-weight",'');
var formCount = parseInt($("#TOTAL_FORMS").val())-1;
delete.remove();
for (var i = parseInt(count); i<=formCount; i++){
var newName = "form-"+i+"-weight";
$("#id_form-"+(i+1)+"-weight").attr("name",newName);
}
});
I suppose it has something to do with how I select the elements inside the loop because when I use just the variable "i" instead of "newName" it works as expected.
The problem is you're not initializing i properly.
This happens because "count" doesn't contain a string that can be parsed into an integer under the conditions of parseInt, I suggest you look here:
w3Schools/parseInt
Note: If the first character cannot be converted to a number, parseInt() returns NaN.
When you assign a string to "count" you're actually inserting the string "form-i" into the variable.
What you should do is this:
count = name.replace(prefix,'');
count = count.replace("-weight",'');
You should also rename your "delete" variable to "form" or any other descriptive name, as delete is a reserved word in javascript and also an action so it doesn't really suit as a name for an object.
Don't forget to change the id attribute of the item so it'll fit the new name.
As a note, you should probably consider following through some tutorial on Javascript or jQuery, Tuts+ learn jQuery in 30 days is one i'd recommend.
My first impulse is just to solve this a different way.
Live Demo
var $ul = $('ul');
// Add a new ingredient to the end of the list
function addIngredient() {
var $currentIngredients = $ul.children();
var n = $currentIngredients.length;
var $ingredient = $('<li>', {
html: '<input type="text" /> x'
});
$ul.append($ingredient);
renameIngredientElements();
}
// Rename all ingredients according to their order
function renameIngredientElements() {
$ul.children().each(function (i, ingredient) {
var $ingredient = $(ingredient);
var $input = $ingredient.find('input');
var name = 'form-' + i + '-weight';
$input
.attr('id', 'id_' + name)
.attr('name', name);
});
}
// Delete an ingredient
function deleteIngredient($ingredient) {
$ingredient.remove();
renameIngredientElements();
}
// Bind events
$('.add-ingredient').on('click', addIngredient);
$ul.on('click', '.delete-ingredient', function (event) {
var $ingredient = $(event.currentTarget).closest('li');
deleteIngredient($ingredient);
event.preventDefault();
});
As to why your particular code is breaking, it looks like user2421955 just beat me to it.

Use array value as variable name

I need to use jQuery's keyup function to take the value from input html form elements and display said values in a div elsewhere.
The working code looks as follows:
$('#name').keyup(function() {
var name = $(this).val();
$('#name-in-document').html(name);
});
Since I have many identical instances of the above code block, I'd like to use a for loop to loop through an array of values. The catch is the name of the variable in the second line
var name = $(this).val();
would come from the array.
I have tried the following loop, which does not work because (as I understand it) a Javascript variable cannot be named an array value:
var inputsArray = ["phone", "name", "address"];
for (var i = 0; i < inputsArray.length; i++) {
$("#"+inputsArray[i]).keyup(function() {
var inputsArray[i] = $(this).val();
$("#"+inputsArray[i]+"-in-document").html(inputsArray[i]);
})
};
So I have two questions:
Is it true that I cannot use the array values to create a variable in the for loop?
Is there an alternate way to accomplish the same thing (getting the variable names from the array) that might work?
I am just beginning JavaScript and really appreciate any insight. Thank you!
1. It is not true
2. You'll need to make a closure over the variable i or over the value from inputArray[i] and inside the event-bind the keyword this refers to the DOMNode witch triggers the event:
Read more absout closures here How do JavaScript closures work?
var inputsArray = ["phone", "name", "address"],
i = 0,
len = inputsArray.length;
for ( ; i < len; i ++ ) {
makeKeyupBind(inputsArray[i]);
}
function makeKeyupBind( value ) {
$("#" + value).on("keyup", function() {
$("#" + value + "-in-document").html( this.value );
});
}
That variable only exists within the scope of the function passed as a callback for the keyup event so I don't really see the need to give it a dynamic name; you could call it absolutely anything at all and not run into conflicts.
For the alternative approach, I would recommend giving #name (and his friends) a class name, e.g.
<input class="js-key-up" id="name" />
Then you can do away with the array and the for loop altogether. Also, adding new HTML elements would not require adding items to the array.
HTML
<input class="js-key-up" id="phone">
<input class="js-key-up" id="name">
<input class="js-key-up" id="address">
<p id="phone-in-document"></p>
<p id="name-in-document"></p>
<p id="address-in-document"></p>
JavaScript
​
$('.js-key-up').keyup(function (e) {
var id = $(this).attr('id');
$('#' + id + '-in-document').html($(this).val());
});​
I've created a jsfiddle with the code in.
Try this:
var inputsArray = ["phone", "name", "address"];
for (var i = 0; i < inputsArray.length; i++) {
$("#"+inputsArray[i]).keyup(function() {
var valuesArray[i] = $(this).val();
$("#"+inputsArray[i]+"-in-document").html(valuesArray[i]);
})
var inputsArray = ["phone", "name", "address"];
for (var i = 0; i < inputsArray.length; i++) {
$("#"+inputsArray[i]).keyup(function() {
var htmlValue = $(this).val();
$("#"+inputsArray[i]+"-in-document").html(htmlValue);
})
I think you don't need to name variable from array, do you?
You can build a selector straight from the array and skip the loop completely. Use the id of the current input to create the selector for the other element
var inputsArray = ["phone", "name", "address"];
$('#'+ inputsArray.join(',#') ).keyup(){
$('#'+this.id+"-in-document").html( $(this).val() );
})
This will create the selector:
$('#phone,#name,#address')
Above assumes that you are trying to find elements :
$("#phone-in-document").html(val);
$("#name-in-document").html(val);/* etc*/
#Wes Cossick: this line inside of the loop is wrong:
var valuesArray[i] = $(this).val();
if you want to do it that way declare the array before the loop. that is problem of OP
#diana: if i understand you correct, you want to add a dynamic keyup handler to every item in the array? if it is that way, that code should do it (dont reassign items in the array!) the trick is to create a closure (code is untested).
var inputsArray = ["phone", "name", "address"];
for (var i = 0; i < inputsArray.length; i++) {
(function(item) {
$("#"+item).keyup(function() {
$("#"+item+"-in-document").html($(this).val());
});
})(inputsArray[i]);
};
if you are using jQuery (and it seems so ;-), take a look at the each-function in jQuery: http://api.jquery.com/jQuery.each/
that should be a lot easier for you ;-)

Get value of JSON object with inner objects by HTML form field name without eval

I have a problem like this Convert an HTML form field to a JSON object with inner objects but in to the other direction.
This is the JSON Object response from the server:
{
company : "ACME, INC.",
contact : {
firstname : "Daffy",
lastname : "Duck"
}
}
And this is the HTML form:
<form id="myform">
Company: <input type="text" name="company" />
First Name: <input type="text" name="contact.firstname" />
Last Name: <input type="text" name="contact.lastname" />
</form>
And this is the (pseudo)code:
var aFormFields;
for (var i = 0, iMax = aFormFields.length; i < iMax; i++) {
var sFieldName = aFormFields[i].getAttribute('name');
eval("sFieldValue = oResponse."+sFieldName);
}
Ok my solution works, but i looking for a good way to remove the evil eval from the code.
And the solution should also work for form fields with any count of dots in the field name.
Instead of:
eval("sFieldValue = oResponse."+sFieldName);
Use for single dotted fields:
sFieldValue = oResponse[sFieldName];
This will retrieve the value via its key.
Now if you need more than that you need to do the following:
Split sFieldName on .
Loop over that array and go down in oResponse till you reach the value that you desire
Code could look like this:
var node = oResponse, parts = sFieldName.split('.');
while(parts.length > 0) {
node = node[parts.shift()];
}
// node will now have the desired value
Further information on "Member Operators":
https://developer.mozilla.org/en/JavaScript/Reference/Operators/Member_Operators
This works for a single property:
sFieldValue = oResponse[sFieldName]
But it won't work for nested data like contact.firstname.
For that, split the name by dots, and use loop through each name:
var aFormFields;
for (var i = 0, iMax = aFormFields.length; i < iMax; i++) {
var aFieldNameParts = aFormFields[i].getAttribute('name').split(".");
var oFieldValue = oResponse;
for(var j=0; j<aFieldNameParts.length; j++) {
oFieldValue = oFieldValue[aFieldNameParts[j]];
}
var sFieldValue = oFieldValue;
}
Note: if a property does not exist, an error will occur. You might want to check whether oFieldValue[ aFieldNameParts[j] ] exists or not.
While it is possible, I wouldn't loop over the input fields, but over the JSON object:
function fillForm (form, data, prefix) {
prefix = prefix ? prefix + "." : "";
for (var x in data) {
if (typeof data[x] === "string") {
var input = form.elements[prefix + x];
if (input)
input.value = data[x];
} else
fillForm(form, data[x], prefix + x);
}
}
fillForm(document.getElementById("myform"), oResponse);
(untested)
Assuming your naming scheme is consistent, you can convert the dot-notation into subscripts. You'd have to split the field name on the period and iterate or recurse over the tokens, converting each into a subscript. Of course this assumes that oResponse always contains a value for every field.
for (var i = 0; i < aFormFields.length; i++) {
var sFieldName = aFormFields[i].getAttribute('name');
var tokens = sFieldName.split('.');
var cur = oResponse;
for (var j = 0; j < tokens.length; j++) {
cur = cur[tokens[j]];
}
sFieldValue = cur;
}
please treat this as a combination of answer and question :)
i am currently trying to get my server to jsonify the data that i get sent from a form just like you...
in my case the form will in the end create a json object with multiple subobjects that can have subobjects which can have... as well.
the depth is up to the user so i should be able to support infinite recursion.
my "solution" so far just feels wrong, but it correctly does the job,
the function getRequestBody gets fed a req.body object from expressjs,
this is basically an object with the following mapping:
{
"ridic-ulously-deep-subobject": "value",
"ridic-ulously-deep-subobject2": "value",
"ridic-ulously-deep2-subobject3": "value",
}
the following html is in use:
<form>
<input name="ridic-ulously-long-class-string" value="my value" />
</form>
and the javascript function (that should work genericly, feed it a req.body object like above and it will return a json object):
function getRequestBody(reqB){
var reqBody = {};
for(var keys in reqB) {
var keyArr = keys.split('-');
switch(keyArr.length){
case 1:
if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
reqBody[keyArr[0]] = reqB[keys];
break;
case 2:
if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
if(!reqBody[keyArr[0]][keyArr[1]]) reqBody[keyArr[0]][keyArr[1]] = {};
reqBody[keyArr[0]][keyArr[1]] = reqB[keys];
break;
case 3:
if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
if(!reqBody[keyArr[0]][keyArr[1]]) reqBody[keyArr[0]][keyArr[1]] = {};
if(!reqBody[keyArr[0]][keyArr[1]][keyArr[2]]) reqBody[keyArr[0]][keyArr[1]][keyArr[2]] = {};
reqBody[keyArr[0]][keyArr[1]][keyArr[2]] = reqB[keys];
break;
case 4:
// ...
//and so on, always one line longer
}
return reqBody;
}
this just feels wrong and its only covering 5 levels of subobjects right now,
it might happen that an application has enough functionality to reach seven or even ten levels though.
this should be a common problem, but my search effort turned up nothing within 10 minutes,
which usually means that i am missing some keywords
or
that there is no viable solution [yet] (which i cant really imagine in this case).
is there someone out there who has imagination and logic sufficient enough to unspaghettify this or will i just have to expand this function with even more clutter to get me down to 10 possible sublevels?
i think that in the end it wont make a big difference performance wise,
but i would really like NOT to create this awful behemoth :D
have fun
jascha

Categories

Resources