I've been tasked with a demo for a query API our company has. For that, my boss wanted me to create a simple HTML site containing a search feature to use said API. So far, I've managed to create almost everything she wanted, but am now stuck on a rather... Peculiar problem.
I've managed to create a flexible filtering feature, allowing the user to add/remove filters as they see fit. I've done it like this:
window.onload = function init()
{
var filterSet = document.getElementsByTagName("fieldset")[0];
var filterSection = filterSet.getElementsByTagName("section");
var set = filterSection[1];
var elements = set.getElementsByTagName("*");
set.getElementsByTagName("*")[5].addEventListener("click", cloneSection);
if (filterSection.length > 2)
{
elements[6].hidden = false;
elements[6].addEventListener("click", deleteSection);
}
else
{
elements[6].hidden = true;
}
}
function cloneSection(e)
{
var filterSet = document.getElementsByTagName("fieldset")[0];
var filterSection = filterSet.getElementsByTagName("section");
var newId = parseInt(filterSection[filterSection.length - 1].id) + 1;
var set = filterSection[1];
var newSet = set.cloneNode(true);
var elements = newSet.getElementsByTagName("*");
newSet.id = "" + newId;
elements[5].addEventListener("click", cloneSection);
elements[6].hidden = false;
elements[6].addEventListener("click", deleteSection);
filterSet.appendChild(newSet);
}
function deleteSection(e)
{
var target = e.target;
var filterSet = document.getElementsByTagName("fieldset")[1];
var filterSection = target.parentElement;
filterSection.remove();
}
<fieldset>
<section>
<input type="checkbox" id="filter_query" name="feature" value="search" />
</section>
<section id="0">
<input type="text" name="filter_name">
<select id="comperason_type">
<option value="=">Equal to</option>
<option value="!=">Different then</option>
</select>
<input type="text" name="filter_value">
<input type="submit" value="+" id="AddButton">
<input type="submit" value="-" id="RemoveButton" hidden="true">
</section>
</fieldset>
It's rather basic (I'm kinda new to networking and Javascript as a whole, I'll admit), but for the most part it works just fine. Except for one single issue.
I want, when the user adds a new filter, to allow them to remove the default one created by the original markup. The button is there, I just need to set it to hidden = false and that's it. Except... When I try to do that, for some reason, the code doesn't recognize the extra section created into it. For example, if there are 3 sections in the fieldset, one created by my own code, the length count will only return 2.
Does anyone know why? Or what can I do to get it to count properly? I'm sorry if this is a newb question, but like I said, I'm a bit out of my depth here.
EDIT: Made a snippet that works this time. Sorry for making it confusing, but it should be good now.
Related
JSFIDDLE
HTML:
<input type="number" id="strScore" class="attribScore" min=8 max=15>
<input type="number" id="strMod" class="attribMod" readonly="readonly">
Javascript:
/****************************************************************
document.getElementById("strScore").oninput = function update(e) {
var result = document.getElementById("strMod");
var attribScore = $('#strScore').val();
result.value = (Math.floor((attribScore / 2) -5));
}
******************************************************************/
var strScore = $('#strScore').val();
var strMod = $('#strMod').val();
var update = function(score, mod) {
attribMod = (Math.floor(score / 2) - 5);
mod.value = attribMod;
};
update(strScore,strMod);
When the left input is updated with an ability score, the right input should reflect the ability modifier.
The commented section of javascript is perfectly functional, but I would really rather not have a separate function for every input that needs to be updated like this - one function is far easier to isolate and troubleshoot in the future. What I'd like to do is have one function to which I can pass the score and modifier input values as arguments (strScore and strMod in this case) and have it update the modifier field via the .oninput event. My attempt at this is below the commented section of javascript. I feel like I'm just not connecting the dots on how to call the function appropriately or correctly update the Modifier input passed to the function.
Phew. Got pulled away from the desk. Here is a solution for you. You just need to make sure that the strscore is set with an id number. This way you can relate to what strmod you want to change.
Ex. strScore1 = strMod1 and strScore2 = strMod2
This will setup a scenario where you don't have to touch anymore JavaScript to do this same function in the future. Allowing you to add as many score and mod couplets as you want in the HTML part.
We are binding the 'input' event on the class of .attributeScore which allows us to set the function. There is no need to pass in values because they are already included by default. As long as the score input has a class of .attributeScore, then it will fire that function.
We can use this.value to grab the score value, and then sub-string out the identity of the score aka 1 for strScore1 from the this.id attribute of the input field.
If we concatenate that sub-string with #strMod we can update the value of the corresponding strMod attribute with inline math.
Here is the jsfiddle: http://jsfiddle.net/hrofz8rg/
<!DOCTYPE html>
<html>
<head>
<title>Some JavaScript Example</title>
</head>
<body>
<input type="number" id="strScore1" class="attribScore" min=8 max=15>
<input type="number" id="strMod1" class="attribMod" readonly="readonly">
<br>
<br>
<input type="number" id="strScore2" class="attribScore" min=8 max=15>
<input type="number" id="strMod2" class="attribMod" readonly="readonly">
<!-- JavaScript -->
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
$(".attribScore").bind({
'input':function(){
var attrib_num = this.id.substring(8,this.length);
$('#strMod' + attrib_num).val((Math.floor(this.value / 2) - 5));
}
});
</script>
</body>
</html>
Hope that helps! Enjoy!
Modifying your function to accept to dom nodes rather than two values would allow you to reuse the function in separate events that use different dom nodes relatively easily.
/****************************************************************
document.getElementById("strScore").oninput = function update(e) {
var result = document.getElementById("strMod");
var attribScore = $('#strScore').val();
result.value = (Math.floor((attribScore / 2) -5));
}
******************************************************************/
var update = function($score, $mod) {
var attribMod = (Math.floor($score.val() / 2) - 5);
$mod.val(attribMod);
};
document.getElementById("strScore").oninput = function update(e) {
var $score = $('#strScore');
var $mod = $('#strMod');
update($score, $mod);
};
Even better though would be able to dynamically figure out which mod element you should target based on which score element the event was triggered on, then you wouldn't need a separate function to do the calculation/update while keeping the code dry.
I'm a beginner in JavaScript, and i'm writing a simple To-do list application. That accepts user input and adds it as a task in a form of a checkbox.
The problem is that, the code has become more and more repetitive, I tried to make functions for the most repeating parts. But i feel that there's a better way to do that using some kind of DOM native functions.
Here's my code (i'm writing a comment where i feel that there're better choice to make):
window.onload = function(){
submitBtn.addEventListener("click", function(){
//Some code ...
var task = document.createElement("input");
task.id = "task" + i;
task.type = "checkbox";
var taskLabel = document.createElement("label");
taskLabel.htmlFor = "task" + i;
taskLabel.appendChild(document.createTextNode(textBox.value));
//This is from a function i've created to create buttons
var deleteBtn = createButton("delete");
var undoBtn = createButton("undo", "none");
var divideBtn = createButton("divide");
var taskContainer = document.createElement("p");
//A LOT of appendChild is happening .. How can i minimize that using
//native DOM methods
taskContainer.appendChild(task);
taskContainer.appendChild(taskLabel);
taskContainer.appendChild(deleteBtn);
taskContainer.appendChild(divideBtn);
taskContainer.appendChild(undoBtn);
taskPool.appendChild(taskContainer);
//the rest of the code ....
});
}
This is the reason most people use jQuery and template systems like Handlebars or Mustache. It's actually a lot faster, not to mention cleaner to implement and thus easier to maintain, to simply insert your complete view block into the DOM instead of manually creating the individual DOM elements and appending them one by one.
The recommended way to manipulate the DOM using jQuery is to do something like:
var taskName = 'task'+i,
taskLabel = textBox.value,
taskHtml =
'<div>
<input type="checkbox" name="'+taskName+'">
<label for="'+taskNAme+'">'+taskLabel+'</label>
<fieldset>
<button id="btnDel'+i+'">Delete</button>
<button id="btnDiv'+i+'">Divide</button>
<button id="btnUndo'+i+'">Undo</button>
</fieldset>
</div>';
$(taskHtml).appendTo('#TaskPool');
Creating large DOM constructs in a single go through innerHTML is a lot faster than manually creating and appending the individual elements.
However, this still requires mixing HTML and JS, which is pretty ugly. So that's why these days developers opt for templates, which allow you to do something like:
<script id="task-template" type="text/x-handlebars-template">
<div>
<input type="checkbox" name="task{{i}}">
<label for="task{{i}}">{{label}}</label>
<fieldset>
<button id="btnDel{{i}}">Delete</button>
<button id="btnDiv{{i}}">Divide</button>
<button id="btnUndo{{i}}">Undo</button>
</fieldset>
</div>
</script>
Then in your click handler:
var source = $("#task-template").html(),
template = Handlebars.compile(source),
context = {
i: i,
label: textBox.value
},
html = template(context);
$('#TaskPool').append(html);
And from there, you can take it one step further and add two-way data binding, such as through Angular.js, Kockout.js or jsViews (+ jsRender). Then you just have something like this:
<ol id="TasksList">
<li ng-repeat="task in tasks | orderBy:orderProp">
<input type="checkbox" name="{{task.name}}" ng:model="task.checked">
<label for="{{task.name}}">{{task.label}}</label>
<fieldset>
<button ng-click="deleteTask(task.id)">Delete</button>
<button ng-click="divideTask(task.id)">Divide</button>
<button ng-click="undoTask(task.id)">Undo</button>
</fieldset>
</li>
</ol>
And in your controller:
$scope.orderProp = 'id';
$scope.nextId = 0;
$scope.addTask = function(label) {
var id = $scope.nextId++,
task = {
id: id,
label: label,
name: 'task'+id,
checked: false
};
$scope.tasks.push(task);
};
$scope.deleteTask = function(id) { ... };
$scope.divideTask = function(id) { ... };
$scope.undoTask = function(id) { ... };
If you want to minimize your code wherever you can then using a library like jquery would be one of your best options.
However, if in this case you'd prefer to stick to 'pure' javascript, you could avoid all the appends up there, by adding a method to the Node object of the DOM similar to this one:
/* is Node.prototype.multiAppend already defined? */
if( typeof Node.prototype.multiAppend !== "function" ) {
Node.prototype.multiAppend = (function() {
/*get the Array.prototype.slice method to use on the returned function*/
var slice = [].slice;
/*the function multiAppend comes to be*/
return function() {
var i = 0,
max = arguments.length;
for (; i < max; i++) {
this.appendChild(arguments[i]);
}
/*allow chainability*/
return this;
};
})();
}
This would allow you to do something like this:
var item = document.getElementsByClassName('item')[0],
/*create elements*/
h1 = document.createElement("h1"),
div = document.createElement("div"),
p = document.createElement("p");
/*append them to item with just one line*/
item.multiAppend(h1, div, p);
Take a look at the demo here: http://jsfiddle.net/8mjBR/2/ *
Surely, this shouldn't be so hard?
I have a <select>, which has, of course, <options>. These options are always in number format, because they are dynamically added to the list by the user.
I then need to get all of these options from the list, put them an array and then perform logic on the array. I've tried searching around, but everything relates to jquery or php - and I'm using plain old HTML and JavaScript.
The select is in a scrolling-box format:
<select id="selectBox" name="select" size="15" style="width:190px;">
<!-- <options> are added via javascript -->
</select>
Currently, I'm using this JavaScript to get the elements, but it's not working:
//Calculate all numbers
var x=[];
function calculate()
{
for (var i = 0; i < 999; i++)
{
x[i]=selectbox.options[i].value;
alert(x[i]);
}
}
Calculate() is called by a button. Something is going terribly wrong, and I can't work it out. selectbox is previously defined as var selectbox = document.getElementById("selectBox"); and I know this works.
The alert is only being called so I can try to debug the thing...
I'm using the figure of 999 because I can't work out how to get a number of how many elements are in the <select> (because it is in scrolling-box format).
The solution must be javascript, and the listbox must be in that scrolling-box format.
Thanks in advance for your help!
Edit -- Okay, more coding to help this.
<form id="aggregateForm">
<input id="inputNum" value="" type="text"><input id="addnum" value="Add" onclick="add();" type="button">
<br>
<select id="selectBox" name="select" size="15" style="width:190px;">
</select>
<br>
<input id="calculate" value="Calculate" onclick="calculate();" type="button"><input id="reset" value="Reset" onclick="reset();" type="button">
</form>
<script type="text/javascript">
var selectbox = document.getElementById("selectBox");
function add()
{
//Function to add a new number to the list of digits
//Parses an integer to ensure everything works okay
if(IsNumeric(document.getElementById("inputNum").value) == true)
{
selectbox.options[selectbox.options.length] = new Option(document.getElementById("inputNum").value, document.getElementById("inputNum").value);
inputNum.focus();
}
else if(IsNumeric(document.getElementById("inputNum").value) == false)
{
alert("I'm sorry, but you have entered an invalid number. Please enter a number into the input box.");
inputNum.focus();
}
}
//Calculate all numbers
var x=[];
function calculate()
{
for (var i = 0; i <selectbox.options.length; i++)
{
x[i]=selectbox.options[i].value;
alert(x[i]);
}
}
//IsNumeric function coding taken from http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric, code by Joel Coehoorn
function IsNumeric(input)
{
return (input - 0) == input && input.length > 0;
}
</script>
The problem is that calculate is the ID of the element too. And oddly enough it believes that calculate is that DOM object not you function: proof. I changed the function name to calculates.
I only found out last week that you can reference your elements with IDs with said IDs.
<div id="really">Yep for some reason</div>
... later in javascript
// no document.getElementById, just
// let me be very clear, THIS IS WRONG TO USE VERY BAD SO EVERYONE CAN KNOW NOT TO USE THIS
// but it does work this way so be aware
really.innerHTML = "I guess he was right.";
check this jsfiddle
var selectbox = document.getElementById("selectBox");
var x = [];
function calculate()
{
for (var i = 0; i <selectbox.options.length; i++)
{
x[i]=selectbox.options[i].value;
alert(x[i]);
}
}
calculate();
This will alert EVERY option element in the select.
This is an update to my previous question and am posting the entirety of the code as is plus what I'd like to add to it. Thanks to everyone for their support so far.
Essentially I'm trying to build a queue or array that will hold Clip objects, each with three different properties: url, inpoint, and outpoint. This array will be the core of my system which will store different Youtube videos using the Youtube API so that the user can essentially edit and cue up the videos they enter into the array. This array I've called Timeline and should have the ability to push in new objects rearrange other elements etc.
Right now I'm trying to get it to just push in a new object; however, when I try to run the queue it will only display the alert that I should add something to the queue. I've been looking around and can't seem to find a very good example of how to do this so I'm beginning to wonder if my logic for tackling this problem is any good at all.
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="UTF-8">
<title>Building an Array OOP - WIP</title>
<script type="text/javascript">
function Clip() {
this.url = userInput.url.value;
this.in = userInput.in.value;
this.out = userInput.out.value;
}
function Timeline() {
this.clips = [];
this.queue = function() {
this.clips.push(new Clip());
alert("Your url is: " + this.clips.length);
}
this.dequeue = function() {
if(this.clips.length != 0) this.clips.pop();
}
this.runQueue = function() {
if(this.clips.length == 0) {
alert("You need to add something to the queue.");
}
else {
var i = this.clips.length;
var slot = 0;
alert("in else.");
while(i!=0) {
document.getElementById('queue').innerHTML+= this.clips[slot].url + "<br>";
i--;
slot++;
}
}
}
}
var myTime = new Timeline();
</script>
</head>
<body>
<div id="wrapper">
<header id="mainHeader">
<h1>Building an Array with Object Oriented Programming</h1>
</header>
<section id = "addVideo">
<h1> Add a Video </h1>
<form name = "userInput">
<p>
Url: <input type="text" name="url" size="30" />
Start Time: <input type="text" name="in" size="2" />
End Time: <input type="text" name="out" size="2" />
<input type="button" onclick="myTime.queue()" value="Add" />
</p>
</form>
<!-- <table border = "1" id = "tblVideo">
<tr>
<th>Youtube URL</th>
</tr>
</table> //-->
</section>
<aside>
<h1>Show Contents of Timeline</h1>
<p>
<input type="button" onclick="myTime.runQueue()" value="Show Timeline" />
</p>
Your Queue:<br>
<div id="queue"></div>
</aside>
</div>
</body>
I'm curious as to how many of these queue methods I can encapsulate into the Timeline object. I would eventually like to add DOM to update a table with what's been added into the queue and am curious as to whether I should put it in this same object as well or break it off into another function, if so, how do I then pass arguments?
This is an example of a function I've already used in my code that used to take the url and update the table from the queue by simply putting the addTableRow(url); in the function but I'm unsure as to how to implemint this with objects. Would you just write it addTableRow(this.clips.url)? Or should I encapsulate it into the Timeline object?
function addTableRow(url) {
function delayed() {
var table=document.getElementById("tblVideo");
var lastRow = table.rows.length;
// if there's no header row in the table, then iteration = lastRow + 1
var iteration = lastRow;
var row = table.insertRow(lastRow);
var cell1=row.insertCell(0);
cell1.innerHTML=url;
}
setTimeout(delayed,500);
}
I've already made a very simple version of this already without any OOP seen here. I thought that OOP would increase the flexibility of my code and allow me to store multiple objects holding more information.
I apologize if any of this sounds overly simple but I'm kind of curious if OOP is really the way to go for this kind of problem.
Originally my input type was submit, which wouldn't update the array
<input type="submit" onclick="myTime.queue()" value="Add" />
So I changed it to button.
<input type="button" onclick="myTime.queue()" value="Add" />
After researching more the DOM question has grown into its own problem, which requires another question.
I have a simple HTML form that asks a user to input their name, SKU, quantity, and comments. This is for a simple inventory request system.
<html>
<body>
<form id="myForm" method="post">
<input type="submit">
<br>Name: <input type="text" name="form[name]">
<br>SKU: <input type="text" name="form[SKU1]">
<br>Quantity: <input type="text" name="form[quantity1]">
<br>Comment: <input type="text" name="form[comment1]">
</form>
Add item
<script>
var num = 2; //The first option to be added is number 2
function addOption() {
var theForm = document.getElementById("myForm");
var newOption = document.createElement("input");
newOption.name = "form[SKU"+num+"]"; // form[varX]
newOption.type = "text";
theForm.appendChild(newOption); //How can I add a newline here?
optionNumber++;
}
</script>
</body>
</html>
Currently I can only get it working where it will add a single form value. I would like to recreate the entire myForm except for the name field with a single click.
Your post is very old, so presumably you've found an answer by now. However, there are some things amiss with your code.
In the JavaScript code you have
var num = 2;
This is the number that is incremented to keep track of how many "line-items" you will have on the form. In the function addOption(), though, instead of incrementing num you have
optionNumber++;
You never use optionNumber anywhere else. Your code works once, when you add the first item, but since you increment the wrong variable, you are effectively always adding option 2.
Oh, and adding the newline: you need to append a <br> element.