How to implement "add new item" functionality in JavaScript? - javascript

The title isn't very descriptive but I couldn't find a better one. Feel free to edit it.
Basically what I'm looking for is the best way to do the following:
When the user clicks "Add New Item", a new row is added with an indentical text box and drop down as above. The options I can think of are the following:
Hardcode the HTML in the JavaScript code. This is obviously a hideously ugly solution.
Assemble the HTML from DOM nodes (or jQuery objects). This is very ugly too.
Use a client-side template system. I used one of those once and it was pretty weird (it used <script language="html"> tags to define the templates).
Make an ad-hoc client-side "template" and hide it somehow with CSS.
Make an AJAX request to fetch the HTML. This is slow and uses server resources unnecessarily.
What do you suggest? I'm not completely satisfied with any of the above solutions.

Assuming the super-simple approach and that your format is in a table:
<table>
<tr>
<td><input type="text" name="item_name" placeholder="item name" /></td>
<td><select name="item_type"><option value="" selected="selected">Type</option></select></td>
</tr>
<tr>
<td><input type="text" name="item_name" placeholder="item name" /></td>
<td><select name="item_type"><option value="" selected="selected">Type</option></select></td>
</tr>
<tr>
<td colspan="2" id="add">+ Add new item</td>
</tr>
</table>
You can use the following:
$('#add').on('click',function(e){
var $this = $(this);
var $newRow = $this.closest('table').find('tr:first').clone();
$newRow.find(':input').val('');
$newRow.insertBefore($this.parent());
});
Broken down:
We give the last item an ID to make it easier to bind a click event to.
Use jQuery and bind the click event to that ID which:
Grabs the current table we're clicking within ($this.closest('table'))
Locates the first row within that table and duplicates it (.clone())
Remove any populated values that may be present (.find(':input').val(''))
Append this new cloned row to the table just above the "add new item" row ($newRow.insertBefore(...))
You can also take the template approach, but that's really up to you and how much control you'd like over the output.

If you're already using a framework like jquery or sencha on your page, you might as well make use of it.
Otherwise, i'd keep it as simple as possible. This does't look like a very important core functionality, so don't create something that requires extra https requests or even entire libraries to be loaded. Cloning or generating the html might not look elegant, but it'll be:
fast to implement
easy to read, and therefore easy to debug
waste few resources, and extremely fast
stable
doesn't need server side code or extra http requests
it'll be easy to change the implementation later on if needed
Don't create something that's overkill for somerhing as trivial as this.

While I realise you already have an accepted answer, I thought I'd offer a plain JavaScript means of achieving the same:
function closest(el, tag) {
if (!el || !tag) {
return false;
}
else {
var curTag = el.tagName.toLowerCase();
return curTag == tag.toLowerCase() && curTag !== 'body' ? el : closest(el.parentNode, tag);
}
}
function addRow(el) {
if (!el) {
return false;
}
else {
var tr = closest(el, 'tr').previousElementSibling,
newRow = tr.cloneNode(true);
tr.parentNode.insertBefore(newRow, tr.nextSibling);
}
}
document.getElementById('add').onclick = function() {
addRow(this);
}​
JS Fiddle demo.
Revised the above a little, to add a simple shim to cope with those browsers that don't implement previousElementSibling:
function closest(el, tag) {
if (!el || !tag) {
return false;
}
else {
var curTag = el.tagName.toLowerCase();
return curTag == tag.toLowerCase() && curTag !== 'body' ? el : closest(el.parentNode, tag);
}
}
function prevElementSiblingShim(el) {
if (!el) {
return false;
}
else {
var prevSibling = el.previousSibling;
return prevSibling.nodeType == 1 ? prevSibling : prevElementSiblingShim(prevSibling);
}
}
function addRow(el) {
if (!el) {
return false;
}
else {
var par = closest(el, 'tr'),
tr = par.previousElementSibling || prevElementSiblingShim(par),
newRow = tr.cloneNode(true);
tr.parentNode.insertBefore(newRow, tr.nextSibling);
}
}
document.getElementById('add').onclick = function() {
addRow(this);
}​
References:
cloneNode().
insertBefore.
nextSibling.
nodeType.
parentNode.
previousElementSibling Compatibility.
previousSibling.
tagName.
toLowerCase().

Inserting HTML from a string into the DOM is the most efficient way. Unless you're inserting hundreds at once, it doesn't really matter.

Related

How to combine multiple FIlters together to filter Task Rows using jQuery?

I have hacked together a basic example Task List Table with HTML and using jQuery. I have attached some on change events to my Filter DropDown Selection Fields
Demo: http://codepen.io/jasondavis/pen/MwOwMX?editors=101
I have a Filter Selection Field for each of these:
Assigned User
Task Status
Milestone
Priority
Tags
Independently they all work to get the job done in filtering out non matching results from my Task List Table.
For each Task Row, I store the value of each filterable option in a Data Attribute like this example Task Row HTML:
<tr id="task-3"
class="task-list-row"
data-task-id="3"
data-assigned-user="Donald"
data-status="Not Started"
data-milestone="Milestone 1"
data-priority="Low"
data-tags="Tag 3">
<td>Task title 3</td>
<td>11/16/2014</td>
<td>02/29/2015</td>
<td>Low</td>
<td>Milestone 1</td>
<td>Donald</td>
<td>Tag 3</td>
</tr>
So the actual Text for the Task row does not matter because the Task row will not show all the properties. WHat matters is the value stored in the Task Row Data Attributes.
A Task row/record with a Miledstone set to Milestone 2 will have a Data Attribute like this data-milestone="Milestone 2"
An example JavaScript/jQuery Filter code:
// Task Milestone Dropdown Filter
$('#milestone-filter').on('change', function() {
var taskMilestone = this.value;
if(taskMilestone === 'None'){
$('.task-list-row').hide().filter(function() {
return $(this).data('milestone') != taskMilestone;
}).show();
}else{
$('.task-list-row').hide().filter(function() {
return $(this).data('milestone') == taskMilestone;
}).show();
}
});
So as I mentioned. I can get each of my "FIlters" to work by thereself, however as soon as I try to apply more than 1 filter at a time, it will not work with this current code.
I would appreciate any help in modifying my code to make a working multi-filter example please?
My current demo is here: http://codepen.io/jasondavis/pen/MwOwMX?editors=101
Update Test 2
After some thought, I am thinking that perhaps I need to store all the current Filter values into variables and then on each change event instead of this:
return $(this).data('milestone') != taskMilestone;
It would instead need to be more like this...
return $(this).data('milestone') != taskMilestone
&& $(this).data('priority') != taskPriority
&& $(this).data('tags') != taskTags
&& .... for all filters;
Does that sound about right?
Nevermind, just tried this with no luck!
You were close in your second test. Here's a working demo:
http://codepen.io/luciopaiva/pen/oXpzGw?editors=101
I refactored your code a little and centralized the logic around updateFilters(), which is called every time any change event occurs. It starts by assuming each row should be shown and then tests against each filter that is different from the default value (be it 'Any', 'None' or undefined).
By the way, if you can change data-user-assigned into data-user, here's a slightly improved code that greatly reduces the number of lines of code:
http://codepen.io/luciopaiva/pen/YXYGYE?editors=101
I'm using call so I can pass the DOM element (referenced through this) to the context of changeFilter().
I also put all filters into an object (filters) so I can access each one by its name, like filters[filterName] and be able to automate things.
It's worth mentioning that filters variable is global and the whole thing should be put inside an IIFE.
But let's continue. You can go even further and remove the boilerplate code for each change event, considering you can rename element #assigned-user-filter to #user-filter:
http://codepen.io/luciopaiva/pen/YXYGaY?editors=101
The Javascript of this final approach:
(function () {
var
filters = {
user: null,
status: null,
milestone: null,
priority: null,
tags: null
};
function updateFilters() {
$('.task-list-row').hide().filter(function () {
var
self = $(this),
result = true; // not guilty until proven guilty
Object.keys(filters).forEach(function (filter) {
if (filters[filter] && (filters[filter] != 'None') && (filters[filter] != 'Any')) {
result = result && filters[filter] === self.data(filter);
}
});
return result;
}).show();
}
function bindDropdownFilters() {
Object.keys(filters).forEach(function (filterName) {
$('#' + filterName + '-filter').on('change', function () {
filters[filterName] = this.value;
updateFilters();
});
});
}
bindDropdownFilters();
})();
Here I used the same logic as in the second approach, using filters names to reference each dropdown. Classic boilerplate!

Optimizing javascript performance with many event listeners

I am currently building a site that allows searching for elements, with results added to a big table (think hundreds of results). Each element in the result table can be manipulated in various ways (custom javascript 0-5 star rating, toggling a fold-out panel for additional options etc.).
If I use the site on my android tablet, performance of the javascript part is very sluggish, so I am wondering how I can improve performance.
One option I have considered is to not bind any event listeners on the result rows except for a single mouse-enter event, and then binding the actual event listeners only when the mouse is over a given element.
Any other ideas to improve performance would be greatly appreciated.
Most of my javascript code is jquery based, so if you have any jquery specific optimizations I would appreciate that also.
You might look into javaScript event delegation. There are many answers on SO (an example here) and many good articles ( here or here ).
Basically the idea you had is actually a good solution. So, instead of binding - let's say - one hundred rows with their own event handlers, you bind only their common parent which will fire an event when any of its child will receive a mouse input.
Roughly speaking instead of this:
$('tr').on("click", function() {});
You will do this:
$('table').on('click', 'tr', function() {});
This is obviously a very simplified example, but it should be enough to build a good pattern upon.
As a side note, it's a very interesting thing (well, at least for me...) to inspect the event that is fired doing something like:
$('table').on('click', 'tr', function(evt) {
console.log(evt);
});
And see how much information an event carries, and how much useful information you get out of the box with a simple click or mouse enter.
VanillaJs
Of course the same result can be achieved without any library.
A simple implementation using Vanilla JS can be taken from David Walsh's article linked at the beginning of the answer, it goes roughly like this:
// Get the element, add a click listener...
document.getElementById("#myTable").addEventListener("click", function(e) {
// e.target is the clicked element.
// Check if it was a <tr>...
if(e.target && e.target.nodeName == "TR") {
// Element has been found, do something with it
}
});
If you try this code though chances are that the actual target.element is the <td>, and not the <tr> we want.
This means we have to be a bit more clever and walk the DOM upwards to see if the clicked element (<td>) is contained in the one we really want (<tr>).
A rough implementation to get you started would look like this:
function walk(startEl, stopEl, targetNodeName) {
if (!startEl || startEl === stopEl || startEl.nodeName === 'BODY') {
return false;
}
if (startEl.nodeName === targetNodeName) {
// Found it, return the element
return startEl;
}
// Keep on looking...
return walk(startEl.parentNode, stopEl, targetNodeName);
}
const container = document.getElementById('myTable');
container.addEventListener('click', (e) => {
const clickedElm = walk(e.target, container, 'TR');
console.log(clickedElm);
if (clickedElm) {
clickedElm.classList.add('clicked');
}
})
See this fiddle or the snippet below:
function walk(startEl, stopEl, targetNodeName) {
if (!startEl || startEl === stopEl || startEl.nodeName === 'BODY') {
return false;
}
if (startEl.nodeName === targetNodeName) {
return startEl;
}
return walk(startEl.parentNode, stopEl, targetNodeName);
}
const elem = document.getElementById('myTable');
elem.addEventListener('click', (e) => {
const clickedElm = walk(e.target, elem, 'TR');
console.log(clickedElm);
if (clickedElm) {
clickedElm.classList.add('clicked');
}
})
table {
width: 100%;
}
tr {
background: #ddd;
}
.clicked {
background: teal;
}
<table id="myTable">
<tr>
<td>one</td>
</tr>
<tr>
<td>two</td>
</tr>
<tr>
<td>three</td>
</tr>
<tr>
<td>four</td>
</tr>
<tr>
<td>five</td>
</tr>
<tr>
<td>six</td>
</tr>
</table>

manipulate a property of a child element

I have the following row element with a double click event atached to it.
<tr ondblclick="onLabelDoubleClicked(this)">
<td><label id="labelId" >My Label</label></td>
<td><input type="text" id="myInput" data-bind="kendoDropDownList: { data: source, value: myValue, enable: true }" /></td>
</tr>
On double click I need to set the enable property of kendoDropDownList in input element to toggle true/ false.
javascript:
onLabelDoubleClicked = function (event) {
}
I searched through the event properties but could not find anything useful to get the enable property and manipulate it. Any help with working example will be greatly appreciated. Thank You!
Instead of inlining the doubleclick event, it’s easier if you put the handler in the JS code and traverse/change the DOM from there.
I’m not sure about the Kendo stuff in the data-bind property, but it looks like a string to me so you’ll need to do string replaces unless you have a better way.
try this:
$('tr').dblclick(function() {
var $el = $(this).find(':text'),
data = $el.data('bind');
if (/true/.test(data)) {
data = data.replace(/true/,'false');
} else if (/false/.test(data)) {
data = data.replace(/false/,'true');
}
$el.prop('data-bind', data);
});
If you can use jQuery (and tag sets implies you're), why not just use jQuery (and Kendo) methods?
$('tr').dblclick(function() {
var $kd = $(this).find('input').data("kendoDropDownList");
$kd.enable( $kd.element.is(':disabled') );
});

How to migrate a .change function from jQuery to plain Javascript

I'm not a JS expert but i think what i'm trying to do is pretty simple (at least in jQuery)
I've got 3 select
<select id="faq" class="onchance_fill">...</select>
<select id="pages" class="onchance_fill">...</select>
<select id="faq" class="onchance_fill">...</select>
and an input (it's a tinyMCE one in advlink plugin)
<input type="text" onchange="selectByValue(this.form,'linklisthref',this.value);" value="" class="mceFocus" name="href" id="href" style="width: 260px;">
I want that each time i change a value in one of the 3 select, that this value of the option, will be placed in the input.
In Jquery, it would be something like :
$('.ajax_onchance_fill').change(function() {
data = $('.ajax_onchance_fill').val();
$('#href').text(data);
});
But i can't use it. So what is the equivalent in plain Javascript ?
Thanks
I would advice you keep using Jquery as it speeds up this kind of thing but in pure JavaScript i think what you want looks something like this...
<script type="text/javascript">
function load() {
var elements = document.getElementsByClassName('onchance_fill');
for(e in elements){
elements[e].onchange = function(){
document.getElementById('href').value = this.value;
}
}
}
</script>
document.getElementsByClassName("ajax_onchance_fill").onchange = function() {
getElementById('href').value = this.options[this.selectedIndex].text;
};
Though I am not sure exactly if it'll work since getElementsByClassName returns more than 1 element.
Try this:
$('.ajax_onchance_fill').change(function() {
var data = $(this).val();
$('#mytextboxid').val(data);
});
Okay, so I realize this thread is well over 8 years old, so this answer isn't so much for the OP (who probably figured it out long ago) as it is for someone else who might be curious about this particular topic.
All that said, here's a relatively simple and reliable way you could pull it off in vanilla JS:
/**
* Since we need to listen to all three ajax_onchance_fill elements,
* we'll use event delegation.
*
*/
const targetLink = document.getElementById('href');
document.addEventListener('change', function(e) {
if (!!Element.prototype.matches) { // Let's make sure that matches method is supported...
if (e.target.matches('.ajax_onchance_fill')) {
targetLink.textContent = e.target.value;
}
} else { // and if not, we'll just use classList.contains...
if (e.target.classList.contains('ajax_onchance_fill')) {
targetLink.textContent = e.target.value;
}
}
});

How to do this in jQuery?

I have to attach the method copyToLeft on onClick event of all images which are inside the TD. TD is inside the table named mismatchList, so that the structure becomes like this mismatchList > tbody > tr > td > img
Although i have already done this, but that is using plain javascript. What i did was, i manually added copyToLeft(this); method on onClick event of all specified elements at the time of creation. [ This is the step which i want to omit and use jQuery to do this somehow ].
Also definition of copyToLeft goes like this:-
function copyToLeft(obj){
leftObj = getLeftTD (obj); // my method which returns the adjacent Left TD
rightObj = getRightTD (obj);
if ( leftObj.innerHTML != rightObj.innerHTML ) {
leftObj.innerHTML = rightObj.innerHTML;
leftObj.bgColor = '#1DD50F';
}else{
alert ( 'Both values are same' );
}
}
If required copyToLeft method's definition can also be changed. [ just in case you think, jQuery can be used to make this method better :) ]
Edit
Instead of asking another question i am just adding the new requirement :) [ let me know if i am supposed to create new one ]
i have to add copyToLeft method to all images as i specified, but alongwith that image src should be left_arrow.gif, and add copyToRight method if src is right_arrow.gif. Also, how can we get the adjacent left/right TD in jQuery, as i want to replpace my getLeftTD and getRightTD method as well?
If i've understood your question correctly, in jQuery, you'd bind the event as such:
$(document).ready(function() {
$('mismatchList > tbody > tr > td > img').click(copyToLeft);
});
In your copyToLeft function, you don't accept obj as an input parameter, instead this will be the image. $(this) will be a jQuery object, containing the image, should you require it...
You could do something like this to match the image src.
$('#mismatchList > tbody > tr > td > img[src='left_arrow.gif']').click(copyToLeft);
$('#mismatchList > tbody > tr > td > img[src='right_arrow.gif']').click(copyToRight);
It is worth noting that the part matching the image src does use the entire contents of src, so if you move the images to a different directory it will stop working. If you just want to match the end of source you can use $= instead of just =.
Here's a variation on TheVillageIdiots rewrite of your copy left function.
function copyToLeft() {
var cell = $(this).closest('td');
var leftObj = cell.prev();
var rightObj = cell.next();
if ( leftObj.html() != rightObj.html()) {
leftObj.html(rightObj.html());
leftObj.css('background-color','#1DD50F');
} else {
alert ( 'Both values are same' );
}
}
Part of me also thinks it would make sense to just have one copyToSibling function where you check $(this).attr('src') for whether it's left_arrow.gif or right_arrow.gif and act accordingly, rather than the two selectors I posted before.
try this code:
<table id="tbl">
<tbody>
<tr>
<td></td><td><img src="file:///...\delete.png" /></td>
</tr>
<tr>
<td></td><td><img src="file:///...\ok.png" /></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function(){
$("table#tbl img").click(function(){
var td=$(this).parents("td");
var tr=$(td).parents("tr");
var left=$(td).prev("td");
$(left).html($(td).html());
});
});
</script>

Categories

Resources