What is the best way to re-use composed html elements, in particular forms?
Say I have a basic voting form similar to this (in a separate file voteform.html):
<form>
<div>
<input id="bad" type="radio" name="vote" value="-1"/>
<label for="bad" style="background: #b66; width:100px; height:100px; display:inline-block"></label>
<input id="good" type="radio" name="vote" value="+1"/>
<label for="good" style="background: #6b6; width:100px; height:100px; display:inline-block"></label>
</div>
</form>
I now want to let users of my website vote on several questions using the same form. What is the best / cleanest / most elegant way to re-use the above code snippet throughout my website (the questions asked may change dynamically over time, but the answer options in the form would always be the same)?
If i simply .clone() the form or load the content via
var x = $('<div/>').load('./voteform.html');
votes_div.append(x)
the radio buttons do not behave as expected because the ids are the same for each clone and all changes will only affect the first form.
This question explains how to change the id when cloning elements, is this the best option i have? Seems like a bit of a workaround.
What is the best / cleanest / most elegant way to re-use the above code snippet throughout my website?
The simplest answer to that is look at how others did it before: templates, web components, libraries, frameworks. Here's an example with Handlebars.js, but you could just as well use any of the 1000's of others. Handlebars is a good start because it's minimal and has implementations in multiple programming languages, but it just comes down to separation of concerns (separating the data from the view) in the end.
var html = document.getElementById('form-template').innerHTML,
template = Handlebars.compile(html),
container = document.getElementById('questions');
var questions = [
{q: 'Ever squeezed a trigger?'},
{q: 'Ever helped a brother out when he was down on his luck?'},
{q: 'Got a little gouda?'},
{q: 'Hater?'},
{q: 'Wanna see a player get paper?'},
{q: 'You a boss player, you a mack?'},
];
container.innerHTML = template({
questions: questions
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.10/handlebars.min.js"></script>
<form id="questions"></form>
<script type="text/template" id="form-template">
{{#each questions}}
<label>{{ q }}</label>
<div>
<input id="yup{{#index}}" type="radio" name="vote{{#index}}" value="-1"/>
<label for="yup{{#index}}" style="background: #6b6; width:100px;">YUP</label>
<input id="nope{{#index}}" type="radio" name="vote{{#index}}" value="+1"/>
<label for="nope{{#index}}" style="background: #b66; width:100px;">NOPE</label>
</div>
{{/each}}
</script>
If you opt for nesting the template in an {{#each}} loop, use Handlebars' #index to add an index to every question id. You can also use strings instead of objects in the array, then you just have to change the {{q}} in the code above to {{this}}
Note: questions inspired by E-40 - Choices lyrics.
Here's how I might approach it from a purely JavaScript stand-point. I've used some ES6 here for convenience, but that can easily changed to ES5 if necessary.
1) Use a function that returns a template literal.
We can pass a question into the function a have it applied to the template very easily. I've added an extra class called inputs here which will be used to catch the click events from the radio buttons as they bubble up.
function newQuestion(question) {
return `<form>
<div>${question}</div>
<div class="inputs">
<input id="bad" type="radio" name="vote" value="-1" />
<label for="bad" class="square bad"></label>
<input id="good" type="radio" name="vote" value="+1" />
<label for="good" class="square good"></label>
</div>
</form>`
}
2) Set up a list of questions
const questions = [
'Do you like sprouts?',
'Do you like the color blue?',
'Do you like candles?',
'Do you like the ocean?'
];
3) Have some way to record the answers. Here's an empty array.
const answers = [];
4) (From the demo) pick up the id of the container where we're going to place the HTML from the template, and set the question index to 0.
const main = document.querySelector('#main');
let index = 0;
5) When we click on a good or bad id we want some way to handle that click. Here we check the id of the clicked element and then update the answers array depending on the id. We then advance to the next question.
function addAnswer(e) {
const id = e.target.id;
switch (id) {
case 'good':
answers.push(1);
break;
case 'bad':
answers.push(-1);
break;
}
showQuestion(++index);
}
6) The main function that checks to see if we've reached the end of the question array. If not it grabs new HTML by passing in the new question to the newQuestion function and adding it to main. Then we add a click event to inputs. If the questions are complete (in this example) it simply shows the answers array in the console.
function showQuestion(index) {
if (index < questions.length) {
main.innerHTML = newQuestion(questions[index]);
main.querySelector('.inputs').addEventListener('click', addAnswer, false);
}
console.log(answers);
}
7) Kick-starts the voting system.
showQuestion(index);
I don't know if this is the kind of approach you want to take, but I hope it helps in some way.
DEMO
Related
In a demo site I have four text boxes which are grouped into two pairs of two (two dimensions, each dimension has two sets). I use only IDs and a naming scheme to get javascript to read each one individually. Is there a cleaner way of using element selectors to avoid having so many similarly named IDs and duplicated code? Can CSS selectors be used for this purpose?
Example HTML
<form id="user-dimension-form" action="javascript:processDimensionInput();plotScatter()" autocomplete="off">
<div class="user-dimension-area">
<label>Dimension 1 Name
<!-- TODO: better naming scheme in CSS -->
<input class="user-dimension-name" id="user-dimension-feature1-name-input" type="text">
</label>
<div class="user-dimension-entry">
<textarea class="user-dimension-feature-set" id="user-dimension-feature1-set1" rows="16"></textarea>
<textarea class="user-dimension-feature-set" id="user-dimension-feature1-set2" rows="16"></textarea>
</div>
</div>
<div class="user-dimension-area">
<label>Dimension 2 Name
<input class="user-dimension-name" id="user-dimension-feature2-name-input" type="text">
</label>
<div class="user-dimension-entry">
<textarea class="user-dimension-feature-set" id="user-dimension-feature2-set1" rows="16"></textarea>
<textarea class="user-dimension-feature-set" id="user-dimension-feature2-set2" rows="16"></textarea>
</div>
</div>
<button type="submit">Submit</button>
<span id="user-dimension-message"></span>
</form>
Example javascript reading the textareas showing the duplicated code. Should I avoid manually concat together HTML ID strings?
const feature1Set1Input = parseInput("user-dimension-feature1-set1");
const feature1Set2Input = parseInput("user-dimension-feature1-set2");
const feature2Set1Input = parseInput("user-dimension-feature2-set1");
const feature2Set2Input = parseInput("user-dimension-feature2-set2");
Given the above html, I suppose you could do something like the following:
let form = document.getElementById('user-dimension-form');
let inputs = form.querySelectorAll('textarea');
let values = [];
inputs.forEach( input => values.push(input.value) );
Not sure that I would though. Can't see any benefit.
The method I came up with is to use CSS selectors. So each input is named has class something like "user-feature-words feature0 set0" and it is selected by something like
document.querySelector(`.user-feature-words.feature${i}.set${j}`)
Of course since I'm combining CSS selectors, I could've used the same string to get a unique ID.
I have a form.
Please note I must use divs for creating the form drop down and not the select option method etc. It just has to be done that way. The code is below.
<form action="url.asp" method="get">
<div class="search-button"><i class="fa fa-search"></i><input type="submit" /></div>
<div class="search-drop-down">
<div class="title"><span>Choose Category</span><i class="fa fa-angle-down"></i></div>
<div class="list">
<div class="overflow">
<div class="category-entry" id="Category1">Category One</div>
<div class="category-entry" id="Category2">Category Two</div>
</div>
</div>
</div>
<div class="search-field"><input type="text" name="search-for" id="search-for" value="" placeholder="Search for a product" /></div>
<input type="hidden" id="ChosenCategory" name="ChosenCategory" value="CATEGORY1 OR CATEGORY2 (WHICHEVER SELECTED)" />
</form>
As shown in the code above I need to populate the hidden field value as per the chosen option which the user selects in the drop down.
I have used about 20 different variations of getElementById or onFocus functions but cannot get it to work.
The only thing I can get to work is the following JavaScript and it just populates the hidden field value with the first id ignoring completely which one has actually been selected(clicked) by the user;
var div = document.getElementById('DivID');
var hidden = document.getElementById('ChosenCategory');
hidden.value = div.innerHTML;
I'm running classic asp so if there is a vbscript way then great, otherwise if I have to use JavaScript to do it then as long as it does the job I'll be happy still.
A click handler on the options could be used to update the value.
No jQuery or any other external library is needed. Below is a working example. Of course, in your case the input element could be of type hidden, but I made it text here for the sake of demonstration.
//Add the click handlers
var options = document.getElementsByClassName('option');
var i = 0;
for (i = 0; i < options.length; i++) {
options[i].addEventListener('click', selectOption);
}
function selectOption(e) {
console.log(e.target);
document.getElementById('output').value = e.target.id;
}
div {
padding: 10px;
}
div.option {
background-color: #CCC;
margin: 5px;
cursor: pointer;
}
<div>
<div class="option" id="Category1">Category One</div>
<div class="option" id="Category2">Category Two</div>
</div>
<input type="text" id="output" />
You should be able to achieve what you're after with a fairly simple setup involving listening for clicks on two separate <div> elements, and then updating an <input> based on those clicks.
TL;DR:
I've put together a jsfiddle here of what it sounds like you're trying to make work: https://jsfiddle.net/e479pcew/5/
Long version:
Imagine we have 2 basic elements:
A dropdown, containing two options
An input
Here's what it might look like in HTML:
<div class="dropdown">
<div id="option-one">Option 1</div>
<div id="option-two">Option 2</div>
</div>
<input type="text" id="hidden-input">
The JavaScript needed to wire these elements up should be fairly easy, but let me know if it doesn't make sense! I've renamed things throughout to make things as explicit as possible, but hopefully that doesn't throw you off.
One quick thing - this is an incredibly 'naive' implementation of this idea which has a lot of potential for refactoring! However I just wanted to show in the most basic terms how to use JavaScript to make this stuff happen.
So here we go - first things first, let's find all those elements we need. We need to assign variables for the two different dropdown options, and the hidden input:
var optionOne = document.getElementById("option-one");
var optionTwo = document.getElementById("option-two");
var hiddenInput = document.getElementById("hidden-input");
Cool. Next we need to make a function that will come in handy later. This function expects a click event as an argument. From that click event, it looks at the id of the element that was clicked, and assigns that id as a value to our hiddenInput:
function valueToInput(event) {
hiddenInput.value = event.target.id;
}
Great - last thing, let's start listening for the clicks on specific elements, and if we hear any, we'll fire the above valueToInput function:
optionOne.addEventListener("click", valueToInput, false);
optionTwo.addEventListener("click", valueToInput, false);
That should get you going! Have a look at the jsfiddle I already linked to and see if it makes sense - get in touch if not.
Are you allowed to use JQuery in this project? It would make your life a lot easier. You can detect when a div is clicked and populate the hidden field.
This could do it:
$(document).ready(function() {
$('.category-entry').click(function() {
$('#ChosenCategory').val($(this).text()); }); });
I am experiencing odd behavior when data linking an object to a form that led me to re-question what exactly is being data bound?
Basically I have a form that creates new Companies as well as updates them. The actual creation/update is done via ajax, which is why I am using the same form for both purposes. In the case when I have to create a company, everything works as I expect. However when I have to update a company, things don't work like how I expect them to. Please have a look at the following code.
Here is my sample Form HTML:
<div id="result"></div>
<script type="text/x-jsrender" id="CompanyFormTemplate">
<form>
<input type="text" data-link="Company.Name" />
</form>
</script>
Here is my Javascript code:
var app = new CompanyFormContext();
function CompanyFormContext() {
this.Company = {
Name: ''
};
this.setCompany = function (company) {
if (company) {
$.observable(this).setProperty('Company', company);
}
};
};
$(function () {
initPage();
...
if (...) {
// we need to update company information
app.setCompany({ Name: 'Company ABC' });
}
});
function initPage() {
var template = $.templates('#CompanyFormTemplate');
template.link("#result", app);
}
Instead of the form input showing 'Company ABC', it is empty. However if I enter anything in it, then the Company.Name value does change! But while I want the input to data bind to Name property of my Company object, I also want it to be aware of any changes made to the (parent) Company object and update it's data binding to it's Name property accordingly.
So my question is how should I change the way I am writing this code so that I can achieve a data bound both on the root object as well as the property?
The issue you were having was because in your scenario, you have paths like Company.Name for which you want to data-link to changes not only of the leaf property but also to changes involving replacing objects higher up in the path (in this case the Company).
For that you need to use the syntax data-link="Company^Path".
See the section Paths: leaf changes or deep changes in this documentation topic:
http://www.jsviews.com/#observe#deep.
See also the examples such as Example: JsViews with plain objects and array in this topic: http://www.jsviews.com/#explore/objectsorvm.
Here is an update of your jsfiddle, using that syntax: https://jsfiddle.net/msd5oov9/2/.
BTW, FWIW, in your fix using {^{for}} you didn't have to use a second template - you could alternatively have written:
<form class="form-horizontal">
{^{for Company}}
...
<input type="text" data-link="Name" />
{{/for}}
</form>
To respond also to your follow-up question in your comment below, you can associate any 'block' tag with a template. Using tmpl=... on the tag means you have decided to separate what would have been the block content into a separate re-usable template. (A 'partial', if you will). The data context for that template will be the same as it would have been within the block.
So specifically, {{include}} {{if}} and {{else}} tags do not move the data context, but {{for}} and {{props}} do. With custom tags you can decide...
So in your case you could use either {^{for Company tmpl=.../}} or {{include tmpl=.../}} but in the second case your other template that you reference would use <input type="text" data-link="Company^Name" /> rather than <input type="text" data-link="Name" />.
Here are some relevant links:
http://www.jsviews.com/#samples/jsr/composition/tmpl
http://www.jsviews.com/#includetag
http://www.jsviews.com/#fortag
I discovered one way to achieve this. It might seem complex at first but it will make sense once you understand it properly.
(PS: I wish there was a sample like this. I might just blog about it.)
HTML Markup:
<script type="text/x-jsrender" id="CompanyFormTemplate">
<form>
{^{for Company tmpl="#CompanyDetailsTemplate" /}
</form>
</script>
<script type="text/x-jsrender" id="CompanyDetailsTemplate">
<input type="text" data-link="Name" />
</script>
Javascript: No changes needed from code above.
Okay so as I said, the solution might look complicated but it turns out all I really had to do was to set up data binding first on the Company object, and then to it's property objects. I wonder if there is a more elegant solution (i.e. one in which all of this can be achieved in a single template) however this solution ensures that data-binding is happening both on the parent object as well as its' properties.
I have posted a JsFiddle for this solution, so if anyone comes across this problem and wants to understand how this solution would work for their particular problem, they will be able to play with a working solution.
I apologize if this question has been poorly worded. The reason I'm asking here is because I didn't know how to Google it properly.
Basically, I want the client to be able to specify a number (they could type the number in a textbox or there could be a series of numbers in a drop-down box or even radio buttons, i'm very flexible with this) that determines how many set of questions the form will display.
To put it into context to make it hopefully easier to understand:
-The form is for booking tickets
-If the client chose '1' at the start, it would mean one ticket so only one set of questions would be visible
-If the client chooses 2 then they want to book 2 tickets etc etc.
I'm looking for a method to implement this using html, css and/or jquery/javascript if needed.
Many thanks in advance!
you can use sheepit plugin - sheepit for form cloning
go through it. it can help you a lot.
when user chooses number of seats, make a loop for each seat and repeat questions as needed. all in same form.
using php
for ($question = 0 ; $question < $_POST['SeatNumbers'] ; $question ++) { ... }
You then have to cycle thru the total number set of questions, I'm guessing you probably will use a SeatNumber input, in the above code you'll get a $SeatNumber0, $SeatNumber1 and so on.
Check to see if $SeatNumber exists, and process if it does.
Using JS, I define a max number for the options. and when user changes seat number, I show/hide each question with css "display:none"/"display:inline"
If you want to handle html content dynamically, you'll need to use Javascript.
You can hear event from your text field or your selector used for provide the number of ticket.
Try this with jQuery :
$('your_field_selector').on('change') or this $('your_field_selector').on('keyup')
If the event is triggered, you can get the value of the field with the jQuery method called "val()" :
$('your_field_selector').val()
Next insert your new html content in function of the value.
Here, There's also many jQuery method for do that like "append()","insertBefore()","insertAfter()"... etc
I think this is the sort of thing your after.
The amount of sections within the form is generated by the number selected on the slider with each dynamically generated form element having a unique id depending on which ticket its for ie: ticket 3 has a name id of name3 ticket 14 would have a name id of name14 and so on to allow proper usage of each input through your server side post functionality.
here is a working jsfiddle http://jsfiddle.net/fd78gkf3/
Excuse the generic form field names :)
<script>function showValue(newValue)
{
document.getElementById("tAmount").value = newValue;
document.getElementById("tAmountDisplay").innerHTML = newValue;
}
</script>
<script>
$(document).on("click", '#select', function() {
var formTimes = document.getElementById("tAmount").value;
$('#forms').append('<form id="ticketForm">');
for(var i = 1; i < formTimes; i++) {
$('#forms').append(i);
$('#forms').append('<div id="formPanel">');
$('#forms').append('<label>Name</label><input id="name' + i +'" type="text"/>'+'<br/>');
$('#forms').append('<label>Age</label><input id="age' + i +'"type="text"/>'+'<br/>');
$('#forms').append('<label>Gender</label><input id="gender' + i +'" type="text"/>'+'<br/>');
$('#forms').append('</div>');
}
$('#forms').append('<input type="button" id="submit" value="Submit"/>'+'</form>')
});
</script>
<span id="tAmountDisplay">0</span> Tickets
<form action="" method="post">
<input type="range" min="1" max="20" value="1" step="1" width="100px" oninput="showValue(this.value)" />
<input type="hidden" id="tAmount" name="tAmount" value=""/>
<input type="button" id="select" value="Select" />
<div id="forms">
</div>
</form>
I'm making a website using JSP and servlets and I have to now break up a list of radio buttons to insert a textarea and a button. I have got the button and textarea to hide and show when you click on the radio button it shows the text area and button. But this only appears at the top and when there are hundreds on the page this will become awkward so i need a way for it to appear underneath. Here is what my HTML looks like when complied:
<form action="addSpotlight" method="POST">
<table>
<tr><td><input type="radio" value="29" name="publicationIDs" ></td><td>A System For Dynamic Server Allocation in Application Server Clusters, IEEE International Symposium on Parallel and Distributed Processsing with Applications, 2008</td> </tr>
<tr><td><input type="radio" value="30" name="publicationIDs" ></td><td>Analysing BitTorrent's Seeding Strategies, 7th IEEE/IFIP International Conference on Embedded and Ubiquitous Computing (EUC-09), 2009</td> </tr>
<tr><td><input type="radio" value="31" name="publicationIDs" ></td><td>The Effect of Server Reallocation Time in Dynamic Resource Allocation, UK Performance Engineering Workshop 2009, 2009</td> </tr>
<tr><td><input type="radio" value="32" name="publicationIDs" ></td><td>idk, hello, 1992</td> </tr>
<tr><td><input type="radio" value="33" name="publicationIDs" ></td><td>sad, safg, 1992</td> </tr>
<div class="abstractWriteup"><textarea name="abstract"></textarea>
<input type="submit" value="Add Spotlight"></div>
</table>
</form>
Now here is what my JSP looks like:
<form action="addSpotlight" method="POST">
<table>
<%int i = 0; while(i<ids.size()){%>
<tr><td><input type="radio" value="<%=ids.get(i)%>" name="publicationIDs" ></td><td><%=info.get(i)%></td> </tr>
<%i++; }%>
<div class="abstractWriteup"><textarea name="abstract"></textarea>
<input type="submit" value="Add Spotlight"></div>
</table>
</form>
Thanks in Advance
Dean
You can easily move DOM nodes around using Node#insertBefore. (That link is to MDC, but it's a standard method and well-supported.)
Here's an example using Prototype, but you can do it with jQuery or other libraries, or just straight DOM methods like the one linked above (it's just more hassle without a library):
// Called at some point during page init to hook up the event handler
function hookRadioButtons() {
var form;
form = $('theForm'); // Assuming you put an ID on the form
form.observe('click', radioButtonClick);
}
// Event handler for radio button clicks
function radioButtonClick(event) {
var btn, div;
// Get the (extended) DOM element for the button
btn = event.findElement('input[type=radio]');
if (btn) {
// Get the (extended) DOM element for the div
div = $('theDiv'); // Assuming you gave the div an ID
// Starting from the button, go `up` to the table cell,
// then over to the following cell, and then insert the
// div at the top of it
btn.up('td').next().insert({
top: div
});
// Show the div (if it's hidden)
div.show();
}
}
That's completely off the cuff and untested (and full of assumptions), it's just to give you an idea.
you can use after(html) or before(html) you only need to add to every radiobutton a unique value but you already did that with value="<%=ids.get(i)%>"
something like this?
$("input[value='PutIdHere']").parent().after(htmlContent);
Move the div "abstractWriteup" to after the closing table tag. It's invalid code to have it inside a table without a containing row and cell anyway, and this will ensure that it appears below the table.
EDIT
I realise I may have misunderstood - you want the div to appear below the radio button which has been clicked, don't you?
You can't put <div> inside a table. It's invalid and browsers may render it in odd ways, like, as you said, as the top.
You would have to either:
close the table, put the div and then if you need more rows after that open a second table. To ensure two tables have the same row widths, you can set table-layout: fixed on the tables and add <col> elements with explicit widths for the non-liquid columns. Or,
include the textarea/submit in a new <tr><td>.
Having said that, I don't know why you're using a table here when you aren't really doing anything with the columns. A simple <div> for each radio would be easier to handle.
Since you will also only ever have one radio ticked, you can re-use the same textarea/submit elements, and just move them to the right row onclick. With JavaScript disabled they'll just stay at the bottom.
<form id="publications" method="post" action="...">
<% for(int i= 0; i<ids.size(); i++) { %>
<div>
<input type="radio" name="publicationIDs" value="<%= ids.get(i) %>" id="pid_<%= ids.get(i) %>">
<label for="pid_<%= ids.get(i) %>">
<%= info.get(i).replaceAll("&", "&").replaceAll("<", "<").replaceAll("\"", """) %>
</label>
</div>
<% } %>
<div id="abstractsubmit">
<textarea name="abstract"></textarea>
<input type="submit" value="Add Spotlight">
</div>
</form>
Note: <label> added, for better usability (can click on name to select radio). replaceAll here is acting as a poor man's HTML encoder. You need an HTML encoder every time you output text into HTML, otherwise characters like < in the text will mess up the page, and potentially create cross-site-scripting security holes.
Unfortunately, in an appalling oversight, the original version of JSP didn't include an HTML encoder. Many, many libraries for web-related software have one you can use to avoid having to write out all these replaceAll​s. Also, if you're using JSP 2.0+, you should consider using JSTL c: tags rather than scriptlets. <c:out> gives you an output-with-HTML-encoding behaviour by default.
Example script to move the abstract element about:
<script type="text/javascript">
var radios= document.getElementById('publications').elements.publicationIDs;
function update() {
// Get div containing selected radio
//
var selected= null;
for (var i= radios.length; i-->0;)
if (radios[i].checked)
selected= radios[i].parentNode;
// Move abstract div just after it
//
var a= document.getElementById('abstractsubmit');
if (selected===null) {
a.style.display= 'none';
} else {
a.style.display= 'block';
selected.parentNode.insertBefore(a, selected.nextSibling);
}
}
// Bind to all radios, and reflect initial form selectedness state
//
for (var i= radios.length; i-->0;)
radios[i].onclick= function() { setTimeout(update, 0); };
update();
</script>
setTimeout is used here because the onclick event fires before the radio have been selected to reflect the click, so update can't yet read which radio is chosen. The right thing to do would be to use onchange instead, but unfortunately that doesn't fire at a useful time in IE, so onclick is what we're left with.
update is run at the start because when you're doing forms you can never be sure what your initial state is. Browsers often try to remember form fields when returning to a page, which may result in a radio unexpectedly being ticked at start of play.
(You could make this a little shorter with jQuery. But not massively shorter.)