Reputation: 1024
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.
Upvotes: 0
Views: 81
Reputation: 10976
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.
Upvotes: 1
Reputation: 63524
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.
Upvotes: 1