JavaScript work only in first iteration, doesn't affect on the field from second iteration.
forloop Code Sample
{% for i in userlist %}
<input id="a" type="email" value="{{i.email}}">
<input id="b" type="text" value="{{i.date_of_birth}}">
<input id="c" type="text" value="{{i.mobile}}">
{% endfor %}
Button for disable editing
<button onclick="disableProfileEditing()" type="button"> Disable </button>
Button for enable editing
<button onclick="enableProfileEditing()"> Edit</button>
JavaScript function
function disableProfileEditing() {
document.getElementById(a).disabled = true;
document.getElementById(b).disabled = true;
document.getElementById(c).disabled = true;
}
function disableProfileEditing() {
document.getElementById(a).disabled = false;
document.getElementById(b).disabled = false;
document.getElementById(c).disabled = false;
}
Ok regardless of how the for loop is made, what it is doing is making many elements with the same I'd, which is a problem. Instead you can give them all a unique class, or data attribute, but class is simpler and more widely supported, then in your function you have to get all elements with that class (or data attribute) and do the hiding etc.
So for the for loop
{% for i in userlist %}
<input class="email" type="email" value="{{i.email}}">
<input class="dob" type="text" value="{{i.date_of_birth}}">
<input class="mobile" type="text" value="{{i.mobile}}">
{% endfor %}
Just changed the IDs to class, so you can have more than one. Now for the first function just get all of each class (and the second one I'll leave for you to figure out)
function disableProfileEditing() {
Array.apply(0,document.getElementsByClassName("email")).forEach(x=> x.disabled = true);
Array.apply(0,document.getElementsByClassName("dob")).forEach(x=> x.disabled = true);
Array.apply(0,document.getElementsByClassName("mobile")).forEach(x=> x.disabled = true);
}
OR you could have just made a surrounding container div with a unique ID then just made the for loop add elements into that, probably would have been simpler if you want to show or hide them all together
Related
I am trying to compare two fields generated with Jinja, however, without the need of pressing the submit button.
I have used the included validators, standard if cases, etc. None of them work as expected, so I tried Javascript.
In general, I want to:
Compare two fields (domain/server). If their IP addresses are not exactly a 'Confirm' button should be shown.
In order to do that, I need to reload the server-side HTML.
Additionally, I am unable to figure out how to compare the two fields' IP addresses via Javascript.
Here is a sample code:
<div id="home" class="text-center">
Hello, {{ username }}
</div>
<body>
<input type="text" id="domain" size="30">
<input type="text" id="server" size="30">
{% if not compareArecords %}
<button type="button" class="btn btn-danger">Danger</button><br>
{%endif %}
</body>
<script>
document.getElementById("domain").addEventListener("keyup", compareArecords);
document.getElementById("server").addEventListener("keyup", compareArecords);
function compareArecords() {
var text1 = document.getElementById("domain");
var text2 = document.getElementById("server");
if (text1.value === text2.value)
return true;
else
return false;
}
</script>
What's typically done in order not to have to reload the html is to have the element hidden with css until you need to display it. In your example would be something like
<body>
<input type="text" id="domain" size="30">
<input type="text" id="server" size="30">
<button type="button" class="btn btn-danger hidden" id="btn-danger">Danger</button><br>
</body>
<script>
document.getElementById("domain").addEventListener("keyup", compareArecords);
document.getElementById("server").addEventListener("keyup", compareArecords);
function compareArecords() {
var text1 = document.getElementById("domain");
var text2 = document.getElementById("server");
if (text1.value === text2.value)
// Nothing to do here!
else {
var danger_btn = document.getElementById("btn-danger");
danger_btn.classList.toggle("hidden");
}
}
</script>
Css:
.hidden { display: none; }
It should be noted that you can also generate the Danger button entirely with javascript, and that's not overly complicated either, should you want to do that, but it has some drawbacks like separation of concerns.
I have a form in my input.html page and I have a set of input fields that is both programmatically generated by jinja2 and dynamically generated with Javascript. The inputs that are generated by jinja2 all show up in my request.form when I hit submit (i.e. into my POST function), but the dynamically created fields (with the + button next to a given element) aren't showing up. Which is strange, because if I inspect the page after adding a few new inputs, they... should?
Any ideas?
HTML
(explanation: this iterates through a ~dict and x has two text fields called question and tech-key (ex// "int"). This should print out the question with a 'plus sign' to allow the user to add new input fields dynamically. I include one by default (ex// name="{{x['tech_key']}}1" means name="int1". Every new input field that gets added increments (int1, int2, int3, etc...)
<div class="col-md-9">
{% for k,v in inps['sheet'].items() %}
{% for x in v %}
<div class="{{x['tech_key']}}">
<b>{{x['question']}}</b>
<button class="add_form_field" data-key="{{x['tech_key']}}"><b>+</b></button><br>
<div><input type="text" name="{{x['tech_key']}}1" size="80px"></div>
</div>
{% endfor %}
{% endfor %}
</div><br>
JavaScript
(explanation: This allows me for each individual named div (ex: the <div class="int"> above), to add new input fields when I click the + sign next to my product. I have them connected to the original form. They aren't pushing when I hit submit.
// adds new input fields when the + is clicked on a given question
$(document).ready(function() {
var max_fields = 10;
var add_button = $(".add_form_field");
var x = 1;
$(add_button).click(function(e){
use = $(this).attr("data-key")
var wrapper = "."+use
e.preventDefault();
if(x < max_fields){
x++;
var field_name = use + x
$(wrapper).append('<div><br><input type="text" form="input_form" name="'+field_name+'"/ size=80px><a class="'+field_name+'"><button><b>-</b></button></a></div>'); //add input box
}
$("."+field_name).click(function(){
$(this).closest('div').remove(); x--;
})
});
});
HTML copied directly from my page when I add two new input fields:
<div class="int">
<b>Did you conduct any interviews this quarter?</b>
<button class="add_form_field" data-key="int" data-ol-has-click-handler=""><b>+</b></button>
<div>
<br><input type="text" name="int1" size="80px">
</div>
<div>
<br><input type="text" form="input_form" name="int2" size="80px">
<a class="int2"><button><b>-</b></button></a>
</div>
<div>
<br><input type="text" form="input_form" name="int3" size="80px">
<a class="int3"><button><b>-</b></button></a>
</div>
</div>
I'm retrieving an endDate field from Django model using forloop on HTML page.
I want to check whether all the endDate coming are less that today's date or not. For that I'm using JS code.
My code is able to perform checking only on first date retrieved but it's not working on other dates.
Here's my HTML code for retrieving data:
{% for p in object_list %}
<h4>{{ p.jobName }}</h4>
<p><b style="color: red">Expires On: </b><b>{{ p.endDate }}</b></p>
View info
<input type="hidden" id="endDate" name="variable" value="{{ p.endDate }}">
<span id="check"></span>
{% endfor %}
JavaScript code to check date:
<script type="text/javascript">
var endDate = document.getElementById("endDate").value;
var ToDate = new Date();
if(new Date(endDate).getTime()<=ToDate.getTime()){
document.getElementById("check").innerHTML = "Expired";
document.getElementById("check").className = "label danger";
}
else{
document.getElementById("check").innerHTML = "Active";
document.getElementById("check").className = "label success";
}
</script>
I have 2 {{ p.jobName }} right now. First one should show Expired while second should show Active.
My code is working only for first date i.e., for Computer Admin only.
Here's what I'm getting the output:
Can anybody tell me what's the issue?
Thanks
To expand on what I've already said in the comments:
The essence of your problem is here:
{% for p in object_list %}
...
<input type="hidden" id="endDate" name="variable" value="{{ p.endDate }}">
<span id="check"></span>
...
{% endfor %}
An HTML document can only ever have one element of a particular ID (otherwise it doesn't "ID" the element very well - this is part of the HTML spec). In your case, whenever the loop has more than one element, you will end up with multiple elements, all with the endDate ID, as well as multiples with the check ID.
Repeating an ID like this, as well as being wrong in itself, can have a major functional impact - as you're seeing in your code. Namely that since there is only ever supposed to be (at most) one element of a given ID, any Javascript code that runs on the page is entitled to assume that this is the case. And in particular, document.getElementById returns an object representing a single HTML element, as opposed to similar methods such as document.getElementsByClassName which return a "collection" of DOM nodes. (Because a class, unlike an ID, can be applied to multiple elements in the same document.)
So that, in a nutshell, is how to solve the problem you're having. Simply replace the id="endDate" in the above to class="endDate", similarly with check, and alter your javascript to take this into account. Not only do you have to change the getElementById method to getElementsByClassName, you also have to take into account that this returns a collection (which you can loop over with .forEach), and that you need to target the neighbouring .check span rather than the first, or any old random one. The following code is one way to make it work (note the use of ES6 Array.from to convert the collection to a genuine Array so you can use .forEach):
var endDateElts = Array.from(document.getElementsByClassName("endDate"));
var ToDate = new Date();
endDateElts.forEach(function(dateElt) {
var endDate = dateElt.value;
var check = dateElt.nextSibling;
if(new Date(endDate).getTime()<=ToDate.getTime()){
check.innerHTML = "Expired";
check.className = "label danger";
}
else{
check.innerHTML = "Active";
check.className = "label success";
}
});
Based on
{% for p in object_list %}
<h4>{{ p.jobName }}</h4>
<p><b style="color: red">Expires On: </b><b>{{ p.endDate }}</b></p>
View info
<input type="hidden" id="endDate" name="variable" value="{{ p.endDate }}">
<span id="check"></span>
{% endfor %}
I would assume that you have multiple objects with the same ID hence the reason why the JS is only working on the first endDate and check object.
<input type="hidden" id="endDate" name="variable" value="{{ p.endDate }}">
<span id="check"></span>
What you should do:
Use document.querySelectorAll() to get multiple DOM elements
with the same class name.
Iterate through the NodeList and perform the operations that you wish to perform.
const endDates = document.querySelectorAll('.endDate');
const checks = document.querySelectorAll('.check');
let ToDate = new Date();
let i = 0;
for (const endDate of endDates){
if(new Date(endDate.value).getTime()<=ToDate){
checks[i].innerHTML = "Expired";
checks[i].className = "label danger";
}
else{
checks[i].innerHTML = "Active";
checks[i].className = "label";
}
i++;
}
<div class="container-1">
<input type="text" class="endDate" name="variable" value="2019-11-29">
<span class="check"></span>
<br>
</div>
<div class="container-2">
<input type="text" class="endDate" name="variable" value="2019-11-24">
<span class="check"></span>
<br>
</div>
<div class="container-3">
<input type="text" class="endDate" name="variable" value="2019-11-27">
<span class="check"></span>
</div>
Notes
Not very sure why you would want to do the validation in the front-end if you can do it on your views.py. #Robin Zigmond gave a very good suggestion of just doing it on the backend and populate the template with the result.
If a user clicks the save button as the next action after typing street data the onblur action intercepts the onclick and does not trigger the save. However, if you add some padding (30px) and click above the word save it works but below the word Save it does not work, the same as with no padding. I'm certain users will go right from typing text in the input field then click Save which will fail unless they first click somewhere else and then click Save. I’ve provide html and javascript example below. Is there a way using javascript to solve this issue?
<html>
<script>
function showstreet() {
var x = document.getElementById('street').value;
alert(x);
}
function focused() {
document.getElementById('title').style.display='';
document.getElementById('street').value='';
}
function blured() {
document.getElementById('title').style.display='none';
if (document.getElementById('street').value == '') {
document.getElementById('street').value='street';
}
}
</script>
<style>
.pad5 { padding:5px; }
.pad30 { padding:30px; }
</style>
<body>
<div id="title" class="pad5" style="display:none;">STREET NAME</div>
<div>
<input id="street" type="text" name="street" value="street" class="pad5"
onfocus="focused()" onblur="blured()">
</div>
<br>
<div>
<input type="button" value="Save" class="pad30" onclick="showstreet()">
</div>
</body>
</html>
I converted this to jsfiddle but I'm not doing something right (newbie) https://jsfiddle.net/eyo63mav/26/
use onMouseDown instead of onClick in your save button. Then onMouseDown will be fired before onBlur
below is working code
function showstreet() {
var x = document.getElementById('street').value;
alert(x);
}
function focused() {
document.getElementById('title').style.display = '';
document.getElementById('street').value = '';
}
function blured() {
document.getElementById('title').style.display = 'none';
if (document.getElementById('street').value == '') {
document.getElementById('street').value = 'street';
}
}
<div id="title" class="pad5" style="display:none;">STREET NAME</div>
<div>
<input id="street" type="text" value="street" class="pad5" onfocus="focused()" onblur="blured()">
</div>
<br>
<div>
<input type="button" value="Save" class="pad30" onclick="showstreet()">
</div>
Styling rarely makes a difference with events -- now, while that's a blanket statement and in lots of cases we find the styling of an inline element such as a link or a paragraph becoming problematic with inline events such as OnClick and OnFocus, in your case, adding thirty pixels to the size of a button is not your problem.
The problem with your code is that the variable you're assigning your #title's value to is local (it's inside the scope of showstreet(), of which can only be accessed by aforementioned function) -- nevermind that, it's never used again. You save a value to it, it alerts the user, and that's it -- it's never reassigned nor reused, so while it'll forever stay as the street name they entered, you'll never see it unless you apply it to something.
It took me a while to figure out what exactly you're trying to save, but I think I've managed it.
Here's the code I've created:
var streetValue = "Your street will appear here.";
function clickedField() {
// Init title
document.getElementById('title').innerHTML = streetValue;
// Reset field
document.getElementById('street').value = '';
}
function saveValue() {
// Reassign streetValue
streetValue = document.getElementById('street').value;
// Checking if value was left empty
if (streetValue === '') {
document.getElementById('title').innerHTML = "Error: No Street Entered!";
} else {
document.getElementById('title').innerHTML = streetValue;
}
}
(I'm not entirely sure what you had onblur for, but it should be very easy to insert back. If you need some help with that, comment on my reply, I'll be happy to.)
Now if we update the HTML with the approprate functions:
<div id="title" class="pad5" style="">STREET NAME</div>
<div>
<input id="street" type="text" name="street" value="street" class="pad5"
onfocus="clickedField()">
</div>
<br>
<div>
<input type="button" value="Save" class="pad30" onclick="saveValue()">
</div>
I have the following situation: the CVCFormType is a collection of BenefiItemsFormType. Each BenefitItemFormType has one field that is a collection of BenefitGroupFormType.
I want to be able to dynamically add and remove elements.
I followed the instructions here. Of course they must be tweaked as we talk about nested collections.
On the "fixed" side everything is ok. On the dynamic side (to add and remove elements) so far I've implemented only the inner side (adding BenefitGroups) and only for adding fields.
Here is what I get (which is not right). I have a double link on the top Benefit Item (I should have only one), plus the two group of links (of the first benefit
item and of the second one) are not independent (I click on the second of the one above and it adds a field to the one below). I think I'll have to dynamically change the ul class name.
Any help?
Here is a screenshot:
And here is the code:
{% extends "internal.html.twig" %}
{% block content %}
{{ form_start(form) }}
<br><b>CVC</b>
{% for benefititem in form.benefititems %}
<br><b>Benefit Item</b>
{{ form_row(benefititem.comment) }}
<br><b>Benefit Groups</b>
{# <ul class="benefitgroups"> #}
<ul class="benefitgroups" data-prototype="{{ form_widget(benefititem.benefitgroups.vars.prototype)|e }}">
{% for benefitgroup in benefititem.benefitgroups %}
<li>{{ form_row(benefitgroup.name) }}</li>
{% endfor %}
</ul>
{% endfor %}
{{ form_end(form) }}
{% block javascripts %}
<script>
var $collectionHolder;
// setup an "add a benefitgroup" link
var $addBenefitGroupLink = $('Add a Group');
var $newLinkLi = $('<li></li>').append($addBenefitGroupLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of benefit groups
$collectionHolder = $('ul.benefitgroups');
// add the "add a benefitgroup" anchor and li to the benefitgroups ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addBenefitGroupLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addBenefitGroupForm($collectionHolder, $newLinkLi);
});
});
function addBenefitGroupForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a BenefitGroup" link li
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
</script>
{% endblock %}
{% endblock content %}
If it can help here is the generated HTML:
<html>
<head>
<meta charset="UTF-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<title>Welcome!</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<h1>Here you are inside</h1>
<form name="CVC" method="post" action="">
<br><b>CVC</b>
<br><b>Benefit Item</b>
<div> <label for="CVC_benefititems_0_comment" class="required">Comment</label> <input type="text" id="CVC_benefititems_0_comment" name="CVC[benefititems][0][comment]" required="required" maxlength="400" value="b1" /></div>
<br><b>Benefit Groups</b>
<ul class="benefitgroups" data-prototype="<div id="CVC_benefititems_0_benefitgroups___name__"><div> <label for="CVC_benefititems_0_benefitgroups___name___name" class="required">Name</label> <input type="text" id="CVC_benefititems_0_benefitgroups___name___name" name="CVC[benefititems][0][benefitgroups][__name__][name]" required="required" maxlength="100" /></div></div>">
<li><div> <label for="CVC_benefititems_0_benefitgroups_0_name" class="required">Name</label> <input type="text" id="CVC_benefititems_0_benefitgroups_0_name" name="CVC[benefititems][0][benefitgroups][0][name]" required="required" maxlength="100" value="c1b1" /></div></li>
<li><div> <label for="CVC_benefititems_0_benefitgroups_1_name" class="required">Name</label> <input type="text" id="CVC_benefititems_0_benefitgroups_1_name" name="CVC[benefititems][0][benefitgroups][3][name]" required="required" maxlength="100" value="c2b1" /></div></li>
</ul>
<br><b>Benefit Item</b>
<div> <label for="CVC_benefititems_1_comment" class="required">Comment</label> <input type="text" id="CVC_benefititems_1_comment" name="CVC[benefititems][4][comment]" required="required" maxlength="400" value="b2" /></div>
<br><b>Benefit Groups</b>
<ul class="benefitgroups" data-prototype="<div id="CVC_benefititems_1_benefitgroups___name__"><div> <label for="CVC_benefititems_1_benefitgroups___name___name" class="required">Name</label> <input type="text" id="CVC_benefititems_1_benefitgroups___name___name" name="CVC[benefititems][5][benefitgroups][__name__][name]" required="required" maxlength="100" /></div></div>">
<li><div> <label for="CVC_benefititems_1_benefitgroups_0_name" class="required">Name</label> <input type="text" id="CVC_benefititems_1_benefitgroups_0_name" name="CVC[benefititems][6][benefitgroups][0][name]" required="required" maxlength="100" value="c2b2" /></div></li>
</ul>
<div><button type="submit" id="CVC_submit" name="CVC[submit]">Do Something</button></div><input type="hidden" id="CVC__token" name="CVC[_token]" value="MEUAU3VawkCDJ5jTHo5hSTGrgrWS6XUm-UXeEI9onT8" /></form>
Instead of a list I wish to do all this with tables (so adding and removing rows from the table).
The final goal (adding an additional layer) will be the following:
Well, as you say, your problem is on the dynamic side, aka, Client Side.
I gonna post my shot on what you're trying to do here.
But before, a pro-tip: NEVER EVER print html on an attribute, use another technique, like the one I'm using for example, templates (to replace your prototype attr). There are a ton more techniques to do this, I'll just explain mine.
var $outerTemplate;
var $innerTemplate;
var $outerContainer;
jQuery(document).ready(function($) {
$outerTemplate = $('#top-form-template');
$innerTemplate = $('#inner-form-template');
$outerContainer = $("#row-container");
$("#addRow").on('click', function(e){
addOuterForm();
});
$outerContainer.on('click', '.addItem', function (e) {
addInnerForm(e.target.dataset.rowId);
})
$outerContainer.on('click', '.destroy-row', function (e) {
destroyRow(e.target.dataset.rowId);
});
$outerContainer.on('click', '.destroy-item', function (e) {
destroyItem(e.target.dataset.itemId);
});
});
function addOuterForm () {
var compiled = _.template($outerTemplate.html());
var html = compiled({
outerId: _.uniqueId(),
innerId: _.uniqueId()
});
$outerContainer.append(html);
}
function addInnerForm (outerId) {
var compiled = _.template($innerTemplate.html());
var html = compiled({
outerId: outerId,
innerId: _.uniqueId()
});
$outerContainer.find('#row-'+outerId).find('.benefitgroups').append(html);
}
function destroyRow(id){
$("#row-"+id).remove();
}
function destroyItem(id){
$("#item-"+id).remove();
}
What I did is create 2 templates, one for the outer form (the one with benefit item & group) and other with an inner form (the extra benefit items). Then attach & removing them using some buttons. I encorage you to take a look at templating engines on client side (I notice you know how to use Twig templating engine maybe Handlebars will be easy to catch for you).
I'm using lodash templating engine, because its quite simple and lodash tools are very powerful and useful.
To embed multiple collection of forms usually i use self-widget to control templating. For example:
{{form.name}}
<ul id="benefit-items" data-prototype="{{_self.widget_prototype(form.benefitItems.vars.prototype}|e}}">
{% for benefitItem in form.benefitItems %}
{{_self.widget_prototype(benefitItem)}}
{% endfor %}
<li id="add-benefit-item" onclick="addBenefitItem(this);">add benefit</li>
</ul>
{% macro widget_prototype(form) %}
<li class="benefitItem">
{{form.title}}
<ul class="benefit-group" data-prototype="{{form_widget(form.benefitGroups.vars.prototype)|e}}">
</ul>
</li>
{% endmacro %}
It's how i'm used to create multiple collection forms.