How to serialize a form into an object (with tree structure)? - javascript

I have a form
<form>
<input type="text" name="Name" />
<input type="checkbox" name="Feature.Translate" />
<input type="checkbox" name="Feature.Share" />
<input type="submit" value="Convert into an object" />
</form>
I want to convert it in an object
{
Name: "John Connor's Terminator",
Feature:
{
Translate: true // if checked
// Share wasn't checked
}
}
How can I map the form to an object that has this tree structure?

Add this method to help you build the tree
// add keys to an object as a tree
// ["a", "b", "c"] will generate
// a { b: { c: def } }
// def is the value of the leaf node
var AddToTree = function(obj, keys, def)
{
for (var i = 0, length = keys.length; i < length; ++i)
obj = obj[keys[i]] = i == length - 1 ? def : obj[keys[i]] || {};
};
Create a function for a jQuery selector that will convert the form in an object
$.fn.serializeObject = function()
{
var o = {}; // final object
var a = this.serializeArray(); // retrieves an array of all form values as
// objects { name: "", value: "" }
$.each(a, function() {
var ns = this.name.split("."); // split name to get namespace
AddToTree(o, ns, this.value); // creates a tree structure
// with values in the namespace
});
return o;
};
With these two functions define you can set an event on the submit button:
$(":submit").click(function(e){
// contains the object from the form
// respecting element namespaces
var obj = $("form").serializeObject();
});

Something like the following should work:
function serializeData() {
//this is where we'll store our serialized data
var serializedData = {};
//iterate over input, select, and textarea elements
jQuery("input, select, textarea").each(function(index) {
var $element = jQuery(this);
var name = $element.attr("name");
//we only want to serialize the element if it has a 'name' attribute
if(typeof name != "undefined") {
//split on the . to get an array
var parts = name.split(/\./);
//start building the serialized data
var currentPart = serializedData;
for(var i = 0; i < parts.length; i++) {
//if this particular element doesn't already exist in our hash, create it
//and initialize it to an empty hash
if(typeof serializedData[parts[i]] == "undefined") {
currentPart[parts[i]] = {};
}
//if we're currently looking at the very last element in the array then
//it means that we need to set its value to the value of the corresponding
//input element. Otherwise, it means that there are still keys within the
//array and so we set `currentPart` to the new hash that we just created
if(i == parts.length - 1) {
//if the element is a checkbox or a radio, we need to see if it's checked
//instead of looking at its value
if($element.attr("type").toLowerCase() == "checkbox" || $element.attr("type").toLowerCase() == "radio") {
currentPart[parts[i]] = $element.is(":checked");
}
else {
currentPart[parts[i]] = $element.val();
}
}
else {
currentPart = currentPart[parts[i]];
}
}
}
});
console.log(serializedData);
}
Check out the fiddle.
All you need to do now is to bind serializeData to the submit event on the form.

Related

ng-show when dict is not empty

I have tried:
ng-show="Object.keys(config.taggedNgramsDict).length !== 0"
ng-show="config.taggedNgramsDict !== {}"
ng-show="JSON.stringify(config.taggedNgramsDict) !== '{}'"
Here's what config.taggedNgramsDict looks like:
var add = function (word) {
var final_booleans = []
for(var i = 0; i < $scope.number_terms; i++) {
final_booleans.push(false)
}
$scope.config.taggedNgramsDict[word] = final_booleans
}
Note: add gets triggered every time a 'word' button is clicked in the frontend.
I have checked:
config.taggedNgramsDict is the proper var name
the Dict does become filled and emptied successfully, based on a function in controller.js
'===', '==',''!==' and '!='

Save multiple inputs to localStorage

I'm trying to save multiple inputs to localStorage. I used a 'for' loop for this purpose, but it saved the same value for all three inputs. I want the separate foreach values, not all of them at once:
<script>
document.addEventListener('DOMContentLoaded', function() {
var j = document.getElementsByName('emploi').length;
var i;
for(i=0; i<=j; i++) {
var x = document.getElementsByName('emploi')[i];
x.value = localStorage['emploi'];
x.onchange = function() {
localStorage['emploi'] = this.value;
}
}
});
</script>
<!--Html-->
<!--SALE-->
<input type="text" name='emploi' placeholder='Sale'>
<!--les prof-->
<input type="text" name='emploi' placeholder='Professeur'>
<!--les classes-->
<input type="text" name='emploi' placeholder='Class'>
Store a JSON.stringified object instead of a simple string value otherwise you are using the same value for all 3
document.addEventListener('DOMContentLoaded', function() {
// parse stored JSON if it exists otherwise an empty object
var values = JSON.parse(localStorage.getItem('emploi') || '{}');
var inputs = document.getElementsByName('emploi');
for (let i = 0; i < inputs.length; i++) {
var x = inputs[i];
x.value = values[i] || '';// stored value if it exists or empty string
x.onchange = function() {
// assign value to the object above
values[i] = this.value;
// store updated version of object
localStorage.setItem('emploi', JSON.stringify(values));
}
}
});
It seems that your main issue is knowing how to save data to localStorage. You need to rewrite your code based on the syntax below.
Syntax for SAVING data to localStorage:
localStorage.setItem("key", "value");
Syntax for READING data from localStorage:
const lastname = localStorage.getItem("key");
Syntax for REMOVING data from localStorage:
localStorage.removeItem("key");

Updating multi-level object value from dataset value

difficult one to explain.
var options = {
container: node,
pin: {
size: [50, 50],
anchor: 0,
animation: 0
}
}
Let's use the above Object as an example. I want to loop through the dataset from an HTMLElement and update the above values using the dataset values. This removed the need to manually check if the dataset value exists and then replace the value.
What I have got so far:
(function() {
for( const data in node.dataset ) {
// First remove the 'map' as this isn't required, then split any values with multiple capitals,
// as these corrospond to multilevel object values.
var key = (data.replace("map", "")).split(/(?=[A-Z])/), value = node.dataset[data];
const findOption = function() {
};
// Check that there is a value
if (value !== null) {
const opt = null;
// Find the corresponding default option
}
}
}.call(_));
Here is the HTML with the dataset attributes, this should help everything make more sense:
<div data-map data-map-offset='[10, 10]' data-map-pin-size='[20, 20]'></div>
As you can see above the attribute data-map-pin-size needs to replace the value within the object, but i'm not sure how to reference that object as usually I would either do options.pin.size or options['pin']['size']. But due to this loop not knowing how deep it needs to go I can't always rely on this, I need some kind of callback function right? Which is why I started findOption() however i'm not too sure where to go from there!
Edit:
This is what I have got so far now, however this isn't updating the options object, it's just setting the value of opt.
(function() {
for( const data in node.dataset ) {
// First remove the 'map' as this isn't required, then split any values with multiple capitals,
// as these corrospond to multilevel object values.
var key = (data.replace("map", "")).split(/(?=[A-Z])/), value = node.dataset[data];
// Pin Size
const findOption = function(val) {
return options[val.toLowerCase()];
};
// Check that there is a value
if (value !== null) {
var opt = null;
// Find the corresponding default option
for (var x = 0; key.length > x; x++) {
opt = findOption(key[x]);
}
opt = value;
}
}
console.log(options);
}.call(_));
If you convert your options to this format:
var options = {
container: node,
pinSize: [50, 50],
pinAnchor: 0,
pinAnimation: 0
}
your implementation would be able to be simplified to this:
for (const key in node.dataset) {
const opt = key.replace(/^map(.)/, (match, c) => c.toLowerCase())
options[opt] = JSON.parse(node.dataset[key])
}
assuming you intend to use JSON-compliant values in your HTML data- attributes.
Here I added a recursive function to set the value by finding the specific key on the options object. You can see that for any length of data set attribute the values are getting set properly.
This works with any kind of options object format dynamically.
I added an extra sample as well to demonstrate it.
(function() {
//A function to set the value on a nested object by
//recursively finding the key
function setValue(object, key, value) {
var value;
Object.keys(object).some(function(k) {
if (k === key) {
object[k] = value;
}
if (object[k] && typeof object[k] === 'object') {
setValue(object[k], key, value);
}
});
}
node = document.getElementById("elem");
var options = {
container: node,
pin: {
size: [50, 50],
anchor: 0,
animation: 0,
value: {
xy: [1, 1]
}
}
}
for (const data in node.dataset) {
// First remove the 'map' as this isn't required, then split any values with multiple capitals,
// as these corrospond to multilevel object values.
var keys = (data.replace("map", "")).split(/(?=[A-Z])/),
value = node.dataset[data];
var findOption = function() {
keys.forEach(function(key, index) {
if (index == keys.length - 1) {
setValue(options, key.toLowerCase(), value);
}
})
}();
// Check that there is a value
if (value !== null) {
const opt = null;
// Find the corresponding default option
}
}
console.log(options);
}.call());
<div data-map data-map-offset='[10, 10]' data-map-pin-size='[20, 20]' data-map-pin-value-xy='[0, 5]' id="elem"></div>

"Key" object in jQuery does not work correctly

So I have been trying to get this small bit of code to work. However, I'm not sure if it's my syntax or if I have done this incorrectly. The goal of this code is for the user to input a string like "H2" or "Li" and return the number associated with that string in the div "testing." When I run the code, it does not return anything.
<form>
<input type="text" name="atomNameOne"/>
</form>
<button name="moleRatios">Submit!</button>
<br/>
<br/>
<br/>
<div id="testing"></div>
This is the jQuery code associated with my code.
$('button[name=moleRatios]').click(function(){
var compound = {
H2 = 2.01594,
Li = 6.939,
Be = 9.0122,
B = 10.811
}
var search = function(name) {
for(var key in compound) {
if(compound[key] === name) {
return compound[key];
}
}
};
var $atomValueOne = search($('input[name=atomNameOne]').val());
$('#testing').text($atomValueOne);
});
there is a wrong syntax there:
var compound = {
H2 = 2.01594,
Li = 6.939,
Be = 9.0122,
B = 10.811
}
should be
var compound = {
H2: 2.01594,
Li: 6.939,
Be: 9.0122,
B: 10.811
}
and then
compound[key] will return 2.01594 if key is H2, maybe you want
if(compound[key] === name)
to be
if(key == name)
instead
The rest should be the same
replace
if(compound[key] === name)
with
if(key === name)
and also correct the object syntax as given in other answers
The correct way to define an object is
var compound = { H2: 2.01594, Li : 6.939, Be: 9.0122, B : 10.811 }
as a propertyName : propertyValue format.
To access the propertyName as a string, use-
var search = function(name) {
for(var key in compound) {
if(key === name) {
return compound[key];
}
}
};
where the iterator 'key' will always give the name(as a string) of the property and the format objectName[iterator] will always give the value of the property.

getElementsByName: control by last partial name

I can get div elements by id and using only partial name "first"
html
<div id="first.1.end">first.1.end</div>
<div id="first.2.end">first.2.end</div>
<div id="two.3.end">two.3.end</div>
<div id="first.4.end">first.4.end</div>
js
function getElementsByIdStartsWith(selectorTag, prefix) {
var items = [];
var myPosts = document.getElementsByTagName(selectorTag);
for (var i = 0; i < myPosts.length; i++) {
if (myPosts[i].id.lastIndexOf(prefix, 0) === 0) {
items.push(myPosts[i]);
}
}
return items;
}
var postedOnes = getElementsByIdStartsWith("div", "first");
alert(postedOnes.length);
It counts 3 div elements (alert).
But how can I use end-partial name for search? For example using "end"?
From MDN Attribute selectors:
[attr^=value] Represents an element with an attribute name of attr and
whose value is prefixed by "value".
[attr$=value] Represents an element with an attribute name of attr and
whose value is suffixed by "value".
So you can use [id^="first"] to find elements with id start with "first". and use [id$="end"] to find elements end with "end".
Like
// This find all div which id ends with "end".
var divs = document.querySelectorAll('div[id$="end"]');
or use jQuery:
$('div[id$="end"]');
Also, you can combine multiple attribute selectors altogether to find a more specific element:
// As we only use querySelector, it find the first div with id starts with "two" and ends with "end".
var divStartAndEnd = document.querySelector('div[id^="two"][id$="end"]');
See demo on jsfiddle
Here I am allowing user to pass all three parameters.
suppose user doesn't pass midmatch so it will return only match of first and last.
Below is the working code:
It will return 1 count:
function getElementsByIdStartsWith(selectorTag, firstmatch, midmatch, lastmatch) {
var items = [];
var myPosts = document.getElementsByTagName(selectorTag);
for (var i = 0; i < myPosts.length; i++) {
var firstmatchIndex = firstmatch?myPosts[i].id.indexOf(firstmatch)>-1?true : false : true;
var midmatchIndex = midmatch?myPosts[i].id.indexOf(midmatch)>-1?true : false : true;
var lastmatchIndex = lastmatch?myPosts[i].id.indexOf(lastmatch)>-1?true : false : true;
if (firstmatchIndex && midmatchIndex && lastmatchIndex ) {
items.push(myPosts[i]);
}
}
return items;
}
var postedOnes = getElementsByIdStartsWith("div", "first", "2", "end");
alert(postedOnes.length); // now it will show only one in alert.
It will return 3 count:
function getElementsByIdStartsWith(selectorTag, firstmatch, midmatch, lastmatch) {
var items = [];
var myPosts = document.getElementsByTagName(selectorTag);
for (var i = 0; i < myPosts.length; i++) {
var firstmatchIndex = firstmatch?myPosts[i].id.indexOf(firstmatch)>-1?true : false : true;
var midmatchIndex = midmatch?myPosts[i].id.indexOf(midmatch)>-1?true : false : true;
var lastmatchIndex = lastmatch?myPosts[i].id.indexOf(lastmatch)>-1?true : false : true;
if (firstmatchIndex && midmatchIndex && lastmatchIndex ) {
items.push(myPosts[i]);
}
}
return items;
}
var postedOnes = getElementsByIdStartsWith("div", "first", "", "end");
alert(postedOnes.length); // now it will show only three in alert.
if you don't want to consider any parameter just pass empty string( "" ) while calling the function.
Hope this will help you :)
I guess this kind of selection can be possible by using jQuery + regex. Have a look to this
How can I select an element by ID with jQuery using regex?
Might be some what on the line that you want.

Categories

Resources