How do I dynamically add controls on a hierarchical structure using JavaScript? - javascript

I am trying to create a form dynamically . I should have categories and subcategories and questions . Here is a simple example where I am adding question elements dynamically:
var i = 1; // to hold increment
$('#add').click(function() {
var p = $(this).closest('p'),
i = $(p).length;
$(p).before('<p> <label> Question ' + i + ': <input type="text" id="question_' + i + '"> </label> </p>');
return false;
});
$('#del').click(function() {
$("p label").last().remove();
i -= 1;
return false;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<div>
<p> <label> Question 1: <input type="text" id="question_1"> </label> </p>
<p> Add Question </p>
<p> Del Question </p>
</div>
How can I add categories, so that a category can contain multiple subcategories, which in return can also contain subcategories. Each category should also be able to contain questions. So that my form looks like this :
Test Form
Investment // a category
Real Estate // a subcategory
How much was spent ? // a question
What is the time frame ? // a question
Automobiles // a subcategory
How many vehicles ? // a question
What is the total? // a question
Charity // a category
How much was spent ? // a question
Donations // a subcategory
When started ? // a question
Other // a subcategory
What is the timeframe ? // a question
My current code only creates questions. I tried to create categories but got stuck with the recursion bugs . I tried to read the jtree API but I am not sure it is what I need. I also tried to find a website that would allow to create such a form so that I could take a look at their JavaScript, but could not find such a website. Can someone help me understand how to accomplish this?

The way I would design this would be to have a class(by using a constrcutor function) called "category" in JS such that it can contain other "categories" within itself, maybe in an array. I would make this class also hold a a questions array. Now, without thinking about the html too much, I have a nice and clear model of what the world looks like. In the next step, I will write a single function that can take one of these category instances and generate the dom. This function drawCategory will use a helper function that can probably be an inner function in JS that knows how to generate the dom for a category that does not contain any categories(leaf node in other words), drawCategory uses that helper function intelligently and returns the entire dom for a single category instance. Next I simply iterate over how many ever category instances I have and keep passing the category instance to drawCategory which automagically does the right thing and returns the right dom each time. To avoid multiple dom updates, I would just generate the dom for each of the categories, hold it somewhere, build the entire dom just once and append it to some target element.

edit: Second prototype delivered.
The answer really depends on what you're creating "dynamically", which I'm not clear on. Since I'm not allowed to request clarification until I provide useful answers for rep, here's a jscript that does something useful sorta like what you're saying.
If I really did this myself, I'd integrate an XML file and generate the html entirely from JSON or something. Or at least populate the initial page from js. The solution you see here is horribly sloppy and tightly couples the html to the js.
http://jsfiddle.net/P8X3B/109/ (prototype question adder, other controls present, not implemented)
NEW: http://jsfiddle.net/y29vc5k0/28/ (prototype question and category adder)
/**
***
* questions and categories example
* by Jason D'Aquila
* 23 Jan 2015
* created as prototype of answer to stackoverflow question posted at: http://stackoverflow.com/questions/27772009/
*
* relies on at least jQuery 1.4 ; browsers supporting getter definitions with Object.defineProperty
*/
/* GLOBAL */
function cleaner(text) {
var s = text.replace(/(<|>)/g, '\\$1')
.replace(/ /g, '_')
.replace(/[!"#$%&'()*+,.\/:;<=>?#[\\\]^`{|}~]/g, '');
return s; //can't split a return line
}
/* injective function jQuery objs => String; compact output
* not actually achieved, but this function isn't called in this program anyway
*/
function injectJQueryObjToStr(jqueryObj) {
return ("" + jqueryObj.length) + jqueryObj.attr("id") ? " id: " + jqueryObj.attr("id") : jqueryObj;
//can definitely improve this function
}
canon = ({
/* contract: No enumerable property of canon has the name of an html tag
*/
outputField: $('#out'),
categoriesList: $('#categories'),
/* cannot actually canonize this; see below */
//questionsDropdown: (function () { //references must be invocations ofc
// return $('#questions_del');
//}),
init: function (undef) {
//* //single slash comment toggle
//this.questionsDropDown = (function(nothing) {return nothing;}());
Object.defineProperty(this, "questionsDropdown", {
//cannot actually canonize this
//a setter is only way to "store" a function lookup used with variable access syntax
configurable: true,
enumerable: true,
get: function () {
return $('#questions_del');
}
});
//*/
this.init = undef;
return this;
}
}).init(); //self-initializing object canon
/* CLOSURES */
/* referencing contexts:
* A -- the anonymous function in $('#add') .click
* B -- the anonymous function in $('#cat') .click
*/
//referred by: A, B
var addCategoryIfNotExists = function (desiredName) {
var category_in = desiredName;
var f = cleaner;
//var FF = _compose_ function(x){return 'cat_'+x; } # cleaner
if ($('#cat_' + f(category_in)).length) {
return $('#cat_' + f(category_in));
} else {
$("<p></p>").attr({
id: 'cat_' + f(category_in)
}).html('<label class="cat_' + f(category_in) + '">' + f(category_in) + '</label>').prependTo(canon.outputField);
//another option is .clone()
canon.categoriesList.append($('<option value="' + f(category_in) + '" />'));
return $('#cat_' + f(category_in));
}
};
function inputFieldAt(locale) {
//return $('input', $(locale).closest('p'));
return $(locale).closest('p').find('input');
}
//consts
var QUESTION_PARENT_ELEMENT_TYPE = "p"; //ideally a preprocessor subs this
/* /CLOSURES */
$('#add').click(
//create closure for i=question #
(function () {
var i = 1;
return function () {
var qid, qidlitl;
var category_input;
i = i + 1;
qidlitl = 'question_' + i;
qid = '"question_' + i + '"'; //quotes for HTML attr setting
var category_el;
//* //single-slash comment toggle
//category_input = $('input', $(this).closest('p')).val();
category_input = inputFieldAt(this).val();
category_el = addCategoryIfNotExists(category_input);
//check category_el === canon.outputField.find('#' + 'cat_' + cleaner(category_input) )
/*/
category_el = document.getElementById("out");
//*/
$('<' + QUESTION_PARENT_ELEMENT_TYPE + '></' + QUESTION_PARENT_ELEMENT_TYPE + '>').html('<label for=' + qid + '> Question ' + i + ': </label><input type="text" id=' + qid + '>').appendTo(category_el);
$("<option></option>").attr({
"class": "questions_options",
value: qidlitl
}).text('Question ' + i + '').appendTo(canon.questionsDropdown);
return false; //callback contract
};
})() //SIF to get closure for i = 1 + number of questions ever generated
); //$('#add').click
$('#del').click(function () {
var qselect = canon.questionsDropdown[0]; //This [0] is the inelegance of mixing frameworks
$('#' + qselect.options[qselect.selectedIndex].value + '')
.closest(QUESTION_PARENT_ELEMENT_TYPE).remove();
qselect.remove(qselect.selectedIndex);
return false;
});
$('#cat').click(function () {
//add category and return false exit signal unless add_category returned literal not false (i.e. true)
var category_input;
//category_input = $('input', $(this).closest('p')).val();
category_input = inputFieldAt(this).val();
var res = addCategoryIfNotExists(category_input);
//return !!(res && (res === true)); //!! might get eliminated by compiler?
return res && (res === true) ? true : false; //equality < logical AND < ternary
});
//EOF
The html changed slightly. See the jsfiddle.
So, weeks later, I learn that you actually cannot canonicalize most DOM lookups or jqueries. Here is a jsfiddle with categories and questions. The next prototype will have subcategories, and the final answer will let you delete categories and subcategories with no subcategories or questions.
There is a mystery to me in this jscript. When you add questions, they appear before the one in the html, even though $.appendTo() is used on the containing <\p> .

Related

Removing a p tag based on a conditional jquery

I am creating a google like search application for fun and I am running into an issue.
So right now I am successfully getting the data I want back. And I am showing what I want as well. However, some of the results have undefined for some properties I have rendering. so the page renders them as undefined.
I am attempting to write a conditional that checks if the property is undefined for an individual object. I was able to successfully do this with an image, where if an object does not have an image property, I assign it a generic image. The issue I'm having now, is when an object does not find a particular property, I want to remove the tag associated with displaying that property for that particular object. Yet what is happening is it is removing all the tags with that class name.
function render() {
$.each(loadedBooks, function(index, book) {
checkData();
$('#results').prepend(
"<hr><div id =" + book.id + " class = 'book'><li>" + "<p class='thumbnail'><img src=" + book.volumeInfo.imageLinks.thumbnail + "></img></p>" +
"<p class='title'>" + book.volumeInfo.title + "</p>" +
"<p class ='subtitle'>" + book.volumeInfo.subtitle + "</p>" +
"<p class='authors'>" + book.volumeInfo.authors + "</p></div>"
);
})
}
function checkData() {
for (var i = 0; i < loadedBooks.length; i++) {
if (typeof loadedBooks[i].volumeInfo.subtitle === "undefined") {
$('.subtitle').remove('p');
};
if (!loadedBooks[i].volumeInfo.hasOwnProperty('imageLinks')) {
loadedBooks[i].volumeInfo.imageLinks = {
thumbnail: "http://i.imgur.com/sJ3CT4V.gif"
}
};
}
}
I've been wracking my brain for the past two days trying to get this to work correctly. I had an implementation two days ago before I decided to start over that was successful (very similar tot he typeof above) that DID work, but I'm unsure what I am doing incorrect. Thank you
$('.subtitle') returns an array of all elements with the class subtitle.
That's why in pure JavaScript we write:
document.getElementsByClassName('...'); Elements (pural). While $('#subtitle') would equal to: document.getElementById('...'); Element (singular). So all you need to do is specify which element you want to remove from that array.
replace:
$('.subtitle') with $($('.subtitle')[i])
or in better jQuery, use:
$('.subtitle:eq('+i+')'); or $('.subtitle:nth-of-type('+i+1+')');

Javascript custom sort function depending on user input

I'm trying to make a custom compare callback function for the Javascript array.sort method. It should return a value depending on user input.
When that callback function is called, it should wait until the user clicks a button. Depending on the clicked button the function would know which of the two elements is bigger and return the corresponding result (1 or -1).
The only way I know to wait for user input is with an event listener function, but I don't know how to adapt this to the custom function I have to pass to the array.sort() method.
Here is the code I'm trying with; I know this code won't work:
var array=["a","b","c"];
array.sort(function(a,b){
var result;
$("#button1").click(function(){
result = 1;
});
$("#button2").click(function(){
result = -1;
});
return result;
}
I'm starting to think it's impossible to use the array.sort function for this purpose.
Is there a way to do this?
You could do this the ugly way, by making use of the window.confirm method, which has the behaviour to wait with the further execution of your code until the user closes the pop-up by either choosing OK or Cancel:
array=["a","b","c"];
array.sort(function(a,b){
var a_before_b = confirm("'" + a + "' will be sorted before '" + b
+ "'. Cancel to order differently.");
return a_before_b ? -1 : 1;
});
document.body.innerHTML = '<p>Array sorted: ' + array + '</p>';
<p>You will get 2 to 3 pop up dialogs to sort [a, b, c]</p>
To have the user answer via normal interaction, not via modal dialogs, and still use the standard sort method, is a harder task.
One idea would be to keep a list of answers given, and repeat the sort each time an new answer is available, feeding those answers via the sort callback function.
Once a comparison comes up for which no answer was yet given, you would then present that comparison to the user, and complete the rest of the sort with any order. That sorted array will be ignored, up until the moment you have all answers for all comparisons.
The code is quite long, mostly because of the interaction with the page:
var array = ['a', 'b', 'c'];
var answers = [];
var questionTemplate = "Order '#' before or after '#'?";
var answerTemplate = "The array is sorted. Result: #.";
function logMsg(msg) {
$($('#log')[0].insertRow(-1).insertCell(-1)).text(msg);
}
function answer(order) {
// keep track of answers
answers.push(order);
// show answers also in a log table
logMsg($('#question').text() + ' - ' + (order<0? 'Before' : 'After'));
askNext();
}
function askNext() {
var arrayCopy = array.slice(0); // take real copy
var questionNo = -1;
arrayCopy.sort(function(a,b){
questionNo++
if (questionNo < answers.length) {
// we already asked this, so use that answer
return answers[questionNo];
}
if (questionNo == answers.length) {
// put question to user:
$('#question').text(questionTemplate.replace('#', a).replace('#', b));
}
// don't care for now, just finish it. We need first to
// get an answer, and will then restart the sort.
return -1;
});
if (array.length == answers.length) {
// Array is now sorted according to answers:
// Hide question section
$('#questionDiv').hide();
// Show the result
logMsg(answerTemplate.replace('#', arrayCopy));
}
}
function reset() {
$('#array').text(array);
$('#questionDiv').show()
$('#log').html(''); // empty log table
answers = [];
askNext();
}
// Set up click handlers
$('#reset').click(reset);
$('#before').click(answer.bind(null, -1));
$('#after').click(answer.bind(null, 1));
reset();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>The array <span id="array"></span> will be sorted by asking you questions:</p>
<table id="log"></table>
<div id="questionDiv">
<p id="question" style="font-weight: bold"></p>
<button id="before">Before</button><button id="after">After</button>
</div>
<button id="reset">Restart</button>

Using .length to check if an element exists in JQuery with concated array

Using the common 'if ID exist' method found here, is it still possible check for the existence of the ID when concating the ID with an array variable like below?
for (var i=0; i < lineData.length; i++)
{
optionData = lineData[i].split(",");
if ($("#" + optionData[0]).length)
{
$("#" + optionData[0]).text(optionData[1]);
}
}
When running this in debugging, if the concated $("#" + optionData[0]) ID doesn't exist it yeilds a result of 'undefined: undefined' and jumps to:
Sizzle.error = function( msg ) {
throw "Syntax error, unrecognized expression: " + msg;
in the JQuery code.
Is it proper code etiquette to use check for, and set, HTML ID's in this manner? Why does this not work in the popular 'exist' method? What can I do to fix it and make it skip ID's that don't exist using this type of ID concatenation with an array string?
http://jsfiddle.net/P824r/ works fine, so the problem is not where you think it is. Simplify your code and add in some checks. You're also not doing anything that requires jQuery, so I don't see how this is a jQuery question, but fine:
function handler(data, i) {
var optionData = data.split(","),
$element;
if (optionData[0] && optionData[1]) {
$element = $("#" + optionData[0]);
if ($element.length > 0) {
// omitting >0 as 'trick' causes JS coercion from number to boolean.
// there's literally no reason to ever do so: it's both slower and
// hides your intention to others reading your code
$element.text(optionData[1]);
}
} else { console.error("unexpected optionData:", optionData);
}
lineData.forEach(handler);
but we can do this without jQuery, since we're not really using for anything that we can't already do with plain JS, in the same number of calls:
function handler(data) {
var optionData = data.split(",");
if (optionData.length === 2) {
var id = optionData[0],
content = optionData[1],
element = document.getElementById(id);
// because remember: ids are unique, we either get 0
// or 1 result. always. jQuery makes no sense here.
if (element) {
element.textContent = content;
}
} else { console.error("unexpected input:", optionData);
}
lineData.forEach(handler);
(the non-jquery version unpacks the optionsData into separate variables for improved legibility, but the ultimate legibility would be to make sure lineData doesn't contain strings, but just contains correctly keyed objects to begin with, so we can do a forEach(function(data) { ... use data.id and data.content straight up ... }))
If you want to keep this jQuery-related, there's more "syntax sugar" you're not making use of:
// check for ID in array
jQuery.each(someArray,
function(index, value) {
var the_id = $(value).attr('id');
if ( the_id !== undefined && the_id !== false ) {
// This item has an id
}
});

If else condition Javascript

I have four TextFields on my UI page.I get the input values of the user from all textfields for example values Textfield1.getvalue(),Textfield2.getvalue(),Textfield3.getvalue(),Textfield4.getvalue().
Now on a button click, I need to check which textfields are actually filled by the user and attach those values I need to send a http request to the server to query on the database. These values are used to filter values from a table. SO basically the values are "TABLE COLUMN" values. For this, I thought of using old school combinations like:
if (Textfield1.getvalue()=="" && Textfield2.getvalue()!=""){
//do something
}
else if (Textfield2.getvalue()=="" && Textfield3.getvalue()!=""){
//do something
}
else if (Textfield3.getvalue()=="" && Textfield4getvalue()!=""){
//do something
}
......
and so on.
This, I personally feel is not efficient and not a good programming way. I am pretty sure there might be some other way of doing it which I am not aware of and couldnt find googling it either. Can anyone share some ideas for a better solution.
Thanks in advance.
If you want to do something based on first field that has a value, at least that is what it looks like from your sample, you could do something like:
» Simple Fiddle. «
var do_something = {
0 : function(val) { console.log("Doing x width " + val)},
1 : function(val) { console.log("Doing y width " + val)},
2 : function(val) { console.log("Doing z width " + val)},
3 : function(val) { console.log("Doing w width " + val)},
}
$("#post").on("click", function() {
var val;
$(".test").each(function(i) {
val = $(this).val();
if (val) {
do_something[i](val);
return false; // Break
// (Or omit the break if you want to "do_something" with all fields
// having a value.)
}
});
});
Or, depending on various, a better solution could be:
var do_something2 = {
act1 : function(k, val) { console.log("Doing x width " + val + " k=" + k) },
act2 : function(k, val) { console.log("Doing y width " + val + " k=" + k) },
act3 : function(k, val) { console.log("Doing z width " + val + " k=" + k) }
};
$("#post2").on("click", function() {
var val;
$(".test").each(function(i) {
val = $(this).val();
if (val) {
do_something2[$(this).data("act")](i, val);
return false; // Break
}
});
});
Where you have input fields like this (dynamically or otherwise created):
<input type="text" data-act="act1" class="test" value="one" />
<input type="text" data-act="act2" class="test" value="two" />
This way you can also easily change what action is taken per field simply by setting the data-act value to wanted function.
One idea - check individual fields once and combine into a single unique value:
var c=0;
if (condition1) c+=1;
if (condition2) c+=2;
if (condition3) c+=4;
Etc. now every combination of conditions has a unique value associated with it and you can use a switch statement for cleaner flow.
Think of data instead of control flow. I'd suggest thinking of the problem this way:
Data -> Validation -> Side effects
All those steps must be uncoupled. Here's example, you may have to re-think your data to adapt your code:
// Data
var fields = [Textfield1, Textfield2, Textfield3, Textfield4];
// Validation
var valid = fields.filter(function(x) {
return x.getvalue();
});
// Side effects
valid.forEach(function(field) {
var value = field.getvalue();
// do something
});

Javascript reference by value/by reference problem

I'm creating a jQuery plugin to do paging and encountered the following problem.
When I click on a page link created by the plugin below, it will always give we the value of the last index passed into the value i at the last iterator of the code below. If there are 4 pages, I will always get 4 if I press link 1, 2, 3 or 4. It seems that the reference to the delegate onclick also keeps a reference to the value of i instead of just the value.
Any Ideas? It's the options.onclick(i) that's acting strange.
$.fn.pager = function(options) {
var defaults = {
resultSet: undefined,
onclick: function(page) { alert(page); return false; },
};
return this.each(function () {
var rnd = Math.floor(Math.random()*9999)
var result = '';
for(var i = 1; i <= options.resultSet.PageCount; i++)
{
if(i == options.resultSet.PageCount)
result += '' + i + '';
else
result += '' + i + '' + options.separator;
}
$(this).html(result);
for(var i = 1; i <= options.resultSet.PageCount; i++)
{
$('#' + rnd + '_pagerPage_' + i).click(function() { options.onclick(i) });
}
});
}
I reduced the above code to just the problem case. So some checks re missing ;)
It seems that the reference to the delegate onclick also keeps a reference to the value of i instead of just the value.
What you are experiencing is your first (unexpected) encounter with closures. It's not even a reference that is being passed, it's weirder than that. To understand what's going on (and it's critical that you do if you program in javascript, this is considered basic stuff these days) read my answers to the following related questions:
Please explain the use of JavaScript closures in loops
Hidden Features of JavaScript?
This is a classic problem: the value of i that gets used inside the option click event handler function is whatever value i has at the point at which the event fires (which will be 4, the final value it has in the loop), not the value it had at the point at which you assigned the event handler. The way round it is to create an extra function each time through the loop that has its own variable containing a copy of the value of i at the point it was called:
var createClickHandler = function(n) {
return function() { options.onclick(n); };
};
$('#' + rnd + '_pagerPage_' + i).click( createClickHandler(i) );

Categories