DataTables export for complex HTML (python deform) - javascript

I'm using DataTables jquery for nice-looking tables and python's deform for generating my form elements easily from schema. DataTables exporting functionality doesn't play nice with the form elements generated.
Because of that, I need to use DataTables' export function to properly generate my exports. There's actually four types of cells I can think of in my application (arranged in order of increasing complexity), and the function would need to handle all of them.
Firstly, a standard <input>. Expected output here would just be with some brand
<input name="brand" value="with some brand" id="deformField1168" class=" form-control " type="text">
Secondly, a textarea (this is actually the only one DataTables handles correctly by default, as it strips away HTML to get 'only text contents'). Expected output here would be a short description
<textarea id="deformField1169" name="description" rows="2" class=" form-control ">a short description</textarea>
Third, a stack of divs which I use to place a trash/delete icon on one side of a cell, otherwise this is the same as the first case. If this is the only case which cannot be solved, I don't mind moving the icon to it's own cell, it's just uglier that way. Expected output here would be a_test_item
<div style="float: left; width: 85%; text-align: left">
<input name="item_number" value="a_test_item" id="deformField1167" class=" form-control " type="text">
</div>
<div style="float: right; width: 15%; text-align: right">
<form class="form" action="" method="post" accept-charset="utf-8" enctype="multipart/form-data">
<input name="id" value="1" type="hidden">
<button type="submit" class="btn btn-default" name="delete">
<span class="glyphicon glyphicon-trash">
</span></button>
</form>
</div>
Fourth and last, this set of hidden inputs + javascript which brings up a datepicker. Expected output here would be 2017-03-07
<input name="__start__" value="delivery_date:mapping" type="hidden">
<input name="date" value="2017-03-07" id="deformField1171" class=" form-control hasDatepicker" type="date">
<input name="__end__" value="delivery_date:mapping" type="hidden">
<script type="text/javascript">
deform.addCallback(
'deformField1171',
function deform_cb(oid) {
if (!Modernizr.inputtypes['date'] ||"date" != "date" || window.forceDateTimePolyfill){
$('#' + oid).pickadate({"format": "yyyy-mm-dd", "selectMonths": true, "selectYears": true, "formatSubmit": "yyyy-mm-dd"});
}
}
);
</script>
My current exportOption function looks like this:-
exportOptions: {
format: {
body: function ( data, row, column, node ) {
console.log(data)
return data
}
}
}
I've never used javascript to process strings before (only to getElementByID etc.), and it doesn't seem like string processing is the way to go anyway. Could I create a JS 'page' using these cells and use standard HTML access (all the getElement* functions)?

And as usual the very act of typing out the question provides me with insight which helps. Based on the accepted answer here about creating a DOM from a HTML string, as well as the observation that all visible inputs have the class form-control, here's the (embarrassingly simple) function I've written.
exportOptions: {
format: {
body: function ( data, row, column, node ) {
var div = document.createElement('div')
div.innerHTML = data
var value = div.getElementsByClassName('form-control')[0].value
return value
}
}
}

Related

Cypress iterate over the table and click on the matching text passed from gherkin file

I am new to cypress and have a scenario where i need to select 'text2' from below table which is under a view, 'text2' is the value from feature file.
<table>
<tr .............>
<td ..........>
<div ....>
<input class= ' ' ..... value='text1'>
</div>
</td>
</tr>
<tr .............>
<td ..........>
<div ....>
<input class= ' ' ..... value='text2'>
</div>
</td>
</tr>
</table>
i tried with
cy.get('table tr').find('td').contains('text2').click() its not working,
Any Suggestions would be of great help, Thanks.
Good question, this is actually a bit tricky.
If you follow this Cypress example Find the input[type='submit'] by value,
then your inputs must have the type='submit' attribute for contains() to work.
<div id="main">
<form>
<div>
<label>name</label>
<input name="name" />
</div>
<div>
<label>age</label>
<input name="age" />
</div>
<input type="submit" value="submit the form!" />
</form>
</div>
// yields input[type='submit'] element then clicks it
cy.get('form').contains('submit the form!').click()
However, type='submit' produces buttons on the web page.
If you want input boxes (type='text' which is the default if not specified), you cannot use .contains(). You can access the value of a with .invoke('val').
Sadly however, .invoke('val') does not pinpoint the exact element in the same way .contains() does. It simply gets the text value of the first input and returns the text, not the element (so you can't click it).
The best way I found is to construct a selection function inside a then()
cy.get('table tr td input')
.then($inputs => { // pass in all inputs
return Array.from($inputs) // convert to array
.find(input => input.value === 'text2') // use Array.find() to pick the element
})
.should('have.value', 'text2') // in case 'text2' does not exist
.click()
How about just select the element that contains the test:
cy.contains('text2').click();

What's the cleanest way to implement a dynamic HTML form?

I want to make a HTML form where the user can edit a set of elements at once. The user has to be able to remove an element, edit element and add new elements. I'm using PHP (Laravel) as a backend and jQuery for the dynamic form.
My initial idea was do basically this:
<form id="bars">
#foreach($foo as $bar)
<input type="text" name="name[]" value="{{$bar->name}}" required>
<input type="color" name="color[]" value="{{$bar->color}}" required>
<input type="checkbox" name="completed[]"{{$bar->completed ? ' checked' : ''}}>
<span class="deleteRow"></span>
#endforeach
</form>
<div id="template" style="display:none;">
<input type="text" name="name[]" required>
<input type="color" name="color[]" required>
<input type="checkbox" name="completed[]"{{$bar->completed ? ' checked' : ''}}>
<span class="deleteRow"></span>
</div>
<script>
$(document).ready(function() {
// Remove rows
$('form').on('click', '.deleteRow', function() {
$(this).closest('tr').remove();
});
// Clone new rows from template
$('#addRow').click(function() {
$('#template tr')
.clone()
.appendTo('#bars');
});
});
</script>
This is the Blade template from which I've removed all irrelevant code and styling. This approach uses the following concepts:
[] in the input names so that the POSTed data will be placed into an array. In processing I end up with three arrays name, color, and completed which will contain the POSTed data.
Existing elements are rendered server-side in the form by the Blade template
New rows are cloned from a 'form template' and added to the form when the user clicks the "new row" button.
This is quite simple, and works in most cases (I've used this before), but it does not work in this case because of a small thing: the checkboxes. Unchecked checkboxes will not be POSTed which means that in the completed[] array will be smaller in size than the other arrays and I have no way of checking which elements have the checkbox checked.
Now I could modify my JS in such a way that it keeps track of the indexes and explicitly inserts the index in every input name (so name[0], name[1], etc) but that approach is complicated by the fact that the form must be pre-filled with data and does not start out empty.
I can, instead of filling the data through the Blade template, let the JS handle that too (through a JSON API) but that also gets complicated fast because the JS now has to 'parse' the form template and fill in all the values.
What's the best practise to accomplish this in a clean way?
What I ended up with is the following:
<form id="bars">
#foreach($foo as $i => $bar)
<input type="hidden" name="id[{{$i}}]" value="{{$status->id}}">
<input type="text" class="form-control" name="name[{{$i}}]" value="{{$status->name}}" required>
<input type="color" name="color[{{$i}}]" value="{{$bar->color}}" required>
<input type="checkbox" name="completed[{{$i}}]"{{$bar->completed ? ' checked' : ''}}>
<span class="deleteRow"></span>
#endforeach
</form>
<div id="template" style="display:none;">
<input type="text" name="name[$i]" required>
<input type="color" name="color[$i]" required>
<input type="checkbox" name="completed[$i]"{{$bar->completed ? ' checked' : ''}}>
<span class="deleteRow"></span>
</div>
<script>
$(document).ready(function() {
// Remove rows
$('form').on('click', '.deleteRow', function() {
$(this).closest('tr').remove();
});
// Clone new rows from template
$('#addRow').click(function() {
$('#template tr')
.clone()
.html(function(i, oldHTML) {
return oldHTML.replace(/\$i/g, $('#bars tr').length);
})
.appendTo('#bars');
});
});
</script>
This is mostly what #MagnusEriksson advised me to do (so thanks!) but solved in a slightly cleaner way by not having to keep a counter but simply using the number of rows inside the form as the index for the cloned rows.

Cloning a div but it is not formatted like main div

Here is my main div
<div id="question-con">
<label for="ques-code">Question Setter:</label>
<input type="checkbox" id="ques-code" name="ques-code"/>
<div id="question-toggle" style="display:none;">
<div id="question-div" style="background-color:#A6A6A6;width: 350px;border: 1px solid greenyellow;margin-bottom: 10px;">
<label>Credit</label>
<input type="text" name="credit" class="credit" id="credit_0">
<label>No of Setter</label>
<input type="text" name="setter">
<label>Type</label>
<input type="text" name="type" id="type">
<label for="in-ex">Internal/External</label>
<input type="text" name="in-ex" id="">
<p class="remove" style="color:red;float: right;font-weight: bold;cursor: pointer;" >Remove</p>
</div>
</div>
<button id="btn-question" style="margin-top: 20px;margin-right: 5px; display: none;">Add</button>
</div>
In each button click, below function is triggered.I cloned the main div here, but the format of the cloned div is not like the main div.
$("#btn-question").click(function (e) {
e.preventDefault();
++question_count;
var question_clone = $('#question-div').clone();
question_clone.attr('id', question_count);
//question_clone.children().attr('id', "question_" + question_count);
question_clone.children(".credit").attr('id', "credit_" + question_count);
$('#question-toggle').append(question_clone);
$("#" + question_count + " input").val("");
});
Click here to see the image
What can i do?
You need to pass true as parameters to the clone to get its formatting too:
$('#question-div').clone(true,true);
See the .clone( [withDataAndEvents ] [, deepWithDataAndEvents ] ):
withDataAndEvents
A Boolean indicating whether event handlers and data should be copied along with the elements. The default value is false.
deepWithDataAndEvents
A Boolean indicating whether event handlers and data for all children of the cloned element should be copied. By default its value matches the first argument's value (which defaults to false).
This will only copy formatting of common classes or inline-styles that are applied to the elements but not to specific rules such as if you have #someid > a declared then copying the a element somewhere else such as #otherid a then it will not work. You need to explicitly define the css rules for them.

Javascript Add Row to HTML Table & Increment ID

This is my first post on this site so hopefully you will go easy on me. I'm trying to create an HTML / PHP form and use a small piece of Javascript to add additional rows to a table when a button is clicked and increment the ID for the two fields.
The button works in adding the rows however it doesn't seem to increment the ID, just use the same ID as the previous row. Hopefully someone could help?
$(window).load(function(){
var table = $('#productanddates')[0];
var newIDSuffix = 2;
$(table).delegate('#button2', 'click', function () {
var thisRow = $(this).closest('tr')[0];
var cloned = $(thisRow).clone();
cloned.find('input, select').each(function () {
var id = $(this).attr('id');
id = id.substring(0, id.length - 1) + newIDSuffix;
$(this).attr('id', id);
});
cloned.insertAfter(thisRow).find('input:date').val('');
newIDSuffix++;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="blue-bar ta-l">
<div class="container">
<h1>Submit Your Insurance Renewal Date(s)</h1>
</div>
</div>
<div class="grey-bar">
<div class="container">
<div class="rounded-box">
<div>
<label for="name">Name</label>
<input type="text" id="name" name="name" autocomplete="off" required />
</div>
<div>
<label for="name">Renewal Dates</label>
</div>
<table width="100%" border="0" cellspacing="0" cellpadding="5" id="productanddates" class="border">
<tr>
<td>
<select name="insurance_type1" id="insurance_type1">
<option></option>
<option>Car</option>
<option>Home</option>
<option>Van</option>
<option>Business</option>
<option>GAP</option>
<option>Travel</option>
</select>
</td>
<td>
<input type="date" name="renewal_date1" id="renewal_date1" />
</td>
<td>
<input type="button" name="button2" id="button2" value="+" />
</td>
</tr>
</table>
<div>
<label for="telephone_number">Contact Number</label>
<input type="tel" id="telephone_number" name="telephone_number" pattern="\d{11}" autocomplete="off" required />
</div>
<div>
<label for="email">Email Address</label>
<input type="email" id="email" name="email" autocomplete="off" required />
</div>
<div>
<input name="submit" type="submit" value="Submit" class="btn">
</div>
</div>
cloned.insertAfter(thisRow).find('input:date').val('');
This line isn't correct. It will throw an invalid selector error.
You need to change it to:
cloned.insertAfter(thisRow).find('input[type="date"]').val('');
jQuery actually does support the :INPUT-TYPE format in selectors, but not the new HTML5 input types (yet): so using input[type="date"] here is the correct way for now to select an element with an HTML5 type. Please notice the quotes around the value. If you want to select an attribute with a certain value.
A selector overview of css selectors here: W3schools.
Because this line is throwing an error your newIDSuffix never gets updated, because the script halts at the line before that because of the script error.
#Charlietfl raises a valid point about learning more about classes and DOM traversal. However that will not fix this code. Or explain why your code isn't working. Nevertheless it's a good tip.
I've gone ahead an taken a stab at a cleaner version of what I think that you are trying to accomplish. I'll walk through the major updates:
Updated the button id and name from "button2" to "button1" - I assumed that you would want to keep the indices in sync across the inputs in each row.
Changing $(window).load(function() { to $("document").ready(function() { - While either will work, the former will wait until all images have finished loading, while the latter while fire once the DOM has completed building. Unless you REALLY want the images to load first, I'd recommend $("document").ready(), for faster triggering of the code.
Removing the [0] references - the primary reason to use [0] after a jQuery selector collection is to reference the DOM version of the selected jQuery element, in order to us a "vanilla" JavaScript method on it. In all cases, you were re-rwapping the variables in $(...), which just converted the DOM element back into a jQuery object, so that extra step was not needed.
Changed the .delegate() method to .on() - as Howard Renollet noted, that is the correct method to use for modern versions of jQuery. Note that the "event" and "target" parameters have swapped places in on, from where they were in delegate.
Changed the event target from #button2 to :button - this will make sure that all of the buttons in the new rows will also allow you to add additional rows, not just the first one.
Switched the clone target from the clicked row to the last row in the table - this will help keep your row numbering consistant and in ascending order. The cloned row will always be the last one, regardless of which one was clicked, and the new row will always be placed at the end, after it.
Changed the indexing to use the last row's index as the base for the new row and use a regular expression to determine it - with the table being ordered now, you can always count on the last row to have the highest index. By using the regular expression /^(.+)(\d+)$/i, you can split up the index value into "everything before the index" and "the index (i.e., on or more numbers, at the end of the value)". Then, you simply increment the index by 1 and reattach it, for the new value. Using the regex approach also allows you to easily adapt, it there ever get to be more than 9 rows (i.e., double-digit indices).
Updated both the id and name attributes for each input - I assumed that you would want the id and name attributes to be the same for each individual element, based on the initial row, and, you were only updating the id in your code, which would have caused problems when sending the data.
Changed $("input:date") to $("input[type='date']) - as Mouser pointed out, this was really the core reason why your code was failing, initially. All of the other changes will help you avoid additional issues in the future or were simply "code quality"-related changes.
So . . . those were the major updates. :) Let me know if I misunderstood what you were trying to do or if you have any questions.
$("document").ready(function() {
$('#productanddates').on('click', ':button', function () {
var lastRow = $(this).closest('table').find("tr:last-child");
var cloned = lastRow.clone();
cloned.find('input, select').each(function () {
var id = $(this).attr('id');
var regIdMatch = /^(.+)(\d+)$/;
var aIdParts = id.match(regIdMatch);
var newId = aIdParts[1] + (parseInt(aIdParts[2], 10) + 1);
$(this).attr('id', newId);
$(this).attr('name', newId);
});
cloned.find("input[type='date']").val('');
cloned.insertAfter(lastRow);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="blue-bar ta-l">
<div class="container">
<h1>Submit Your Insurance Renewal Date(s)</h1>
</div>
</div>
<div class="grey-bar">
<div class="container">
<div class="rounded-box">
<div>
<label for="name">Name</label>
<input type="text" id="name" name="name" autocomplete="off" required />
</div>
<div>
<label for="name">Renewal Dates</label>
</div>
<table width="100%" border="0" cellspacing="0" cellpadding="5" id="productanddates" class="border">
<tr>
<td>
<select name="insurance_type1" id="insurance_type1">
<option></option>
<option>Car</option>
<option>Home</option>
<option>Van</option>
<option>Business</option>
<option>GAP</option>
<option>Travel</option>
</select>
</td>
<td>
<input type="date" name="renewal_date1" id="renewal_date1" />
</td>
<td>
<input type="button" name="button1" id="button1" value="+" />
</td>
</tr>
</table>
<div>
<label for="telephone_number">Contact Number</label>
<input type="tel" id="telephone_number" name="telephone_number" pattern="\d{11}" autocomplete="off" required />
</div>
<div>
<label for="email">Email Address</label>
<input type="email" id="email" name="email" autocomplete="off" required />
</div>
<div>
<input name="submit" type="submit" value="Submit" class="btn">
</div>
</div>
cloned.insertAfter(thisRow).find('input[type="date"]').val('');

Adding and removing dom sections with javascript

I want to be able to add new sections (via the 'add' link) and remove them (via the 'x' button) like seen in the image.
The HTML for the image:
<fieldset>
<legend>Legend</legend>
<div id="section0">
<input type="text" name="text1" value="Text1" />
<input type="text" name="text2" value="Text2" size='40' />
<input type="button" value="x" style="width: 26px" /><br />
</div>
add<br />
</fieldset>
I guess I could add new sections as needed (i.e. section1, section2) and delete those sections according to which button was pressed. There would be a javascript function that would inject sections in the DOM everytime the 'add' link was clicked and another for deleting a section everytime the 'x' button was clicked.
Since I have so little experience in HTML and Javascript I have no idea if this is a good/bad solution. So, my question is exactly that: Is this the right way to do it or is there a simpler/better one? Thanks.
P.S.: Feel free to answer with some sample code
Here's one way to do it:
<script type="text/javascript">
function newrow() {
document.getElementById("customTable").innerHTML += "<tr><td><input type='text'></td><td><input type='text'></td><td><button onclick='del(this)'>X</button></td></tr>";
}
function del(field) {
field.parentNode.parentNode.outerHTML = "";
}
</script>
<body onload="newrow()">
<fieldset>
<legend>Legend</legend>
<table>
<tbody id="customTable">
</tbody>
</table>
<button onclick="newrow()">Add</button>
</fieldset>
</body>
You could add IDs to them if you wanted, or you could call them by their position document.getElementsByTagName("input")[x].value The inputs would start at 0, so the left one is 0, right is 1, add row: left is 2, right is 3, etc.
If you delete one, the sequence isn't messed up (it re-evaluates each time), which is better than hard-coded IDs.
I just answered a nearly identical question only a few minutes ago here using jQuery: https://stackoverflow.com/a/10038635/816620 if you want to see how it worked there.
If you want plain javascript, that can be done like this.
HTML:
<div id="section0">
<input type="text" name="text1" value="Text1" />
<input type="text" name="text2" value="Text2" size='40' />
<input type="button" value="x" style="width: 26px" /><br />
</div>
add<br />
Javascript:
function addSection(where) {
var main = document.getElementById("section0");
var cntr = (main.datacntr || 0) + 1;
main.datacntr = cntr;
var clone = main.cloneNode(true);
clone.id = "section" + cntr;
where.parentNode.insertBefore(clone, where);
}​
Working demo: http://jsfiddle.net/jfriend00/TaNFz/
http://pastebin.com/QBMEJ2pq is a slightly longer but robust answer.

Categories

Resources