Remove an item from datalist - javascript

I have a working Ajax call and function that populates a datalist. The datalist is used to select from a finite list an addition to an UL element on the page. Once I add one from the datalist to the actual list, I want to remove that option from the datalist. The brute force method I'm using is to clear the datalist and repopulate it.
function populate_datalist_from_array(list_id, list_str)
{
clearChildren( list_id );
var arr = eval ( list_str );
for (var i = 0; i < arr.length; i++) {
var opt = document.createElement('option');
opt.innerHTML = arr[i];
opt.value = arr[i];
document.getElementById(list_id).appendChild(opt);
}
}
function clearChildren( parent_id ) {
var childArray = document.getElementById( parent_id ).children;
if ( childArray.length > 0 ) {
document.getElementById( parent_id ).removeChild( childArray[ 0 ] );
clearChildren( parent_id );
}
}
I've verified that the list_str object is correct. i.e., it contains only the options not already in the current list. But after calling populate_datalist_from_array with that new list, the datalist in the dropdown doesn't change. Is this because the browser has essentially compiled all of the values that were there (like it were a normal, browser-based autocomplete) and doesn't 'forget' the values that I want removed?

Teemu's JsFiddle works fine. However, it's normally better to avoid recursion, and multiple DOM queries when not required.
Here is an edit that only requires a single DOM query, and is iterative. (Note decrement before index because this is a zero based list)
clearChildren = function (parent_id) {
var parent = document.getElementById(parent_id);
var childArray = parent.children;
var cL = childArray.length;
while(cL > 0) {
cL--;
parent.removeChild(childArray[cL]);
}
};
(In JSFiddle on MacBookPro I saved 10 ms - from 15 ms total - on a list of 500 elements, but could be more dramatic with larger DOM's on mobile).

If list_str really is OK, your code works. You can check it in action at jsFiddle.
The most general reason for a behaviour you've described, is that your code refreshes the page. If you remove type="button" from the "Change options" button in the linked fiddle, you'll get an error (due to the fiddle itself). In your page you probably have something similar invoking populate_datalist_from_array(). Notice, that also hitting Enter on an active text input will do submit/refresh.

Related

How to select next row in an Angular UI grid?

I have a selected row, and by clicking some button (currently I use space via AngularHotkeys.js) I want to deselect current row and select the one that is next after the currently selected one.
The thing is complicated more knowing that I can sort the table with different columns. So, it would be great to know index of the current row with the current sorting applied.
From where do I start with this problem?
Any suggestions are appreciated.
You can get the array of rows, in their sorted and filtered state, from $scope.gridApi.grid.renderContainers.body.visibleRowCache. There's also a bunch of trickiness to deal with when you have the entity and when you have the gridRow, so the code gets a little complex.
Your code would be something like:
$scope.selectNextRow = function() {
var currentRowIndex;
var selectedRows = $scope.gridApi.selection.getSelectedRows();
if( selectedRows.length < 1 ){
// if nothing selected, we'll select the top row
currentRowIndex = -1;
} else {
// if there are multiple selected, we use the first one
var selectedRow = selectedRows[0];
var gridRow = $scope.gridApi.grid.getRow(selectedRow);
currentRowIndex = $scope.gridApi.grid.renderContainers.body.visibleRowCache.indexOf(gridRow);
}
$scope.gridApi.selection.clearSelectedRows();
$scope.gridApi.selection.selectRow($scope.gridApi.grid.renderContainers.body.visibleRowCache[currentRowIndex + 1].entity);
};
Refer http://plnkr.co/edit/Z7HCjVY6oxGJzyjLI6Qo?p=preview
If I understand you correctly you should be able to accomplish what you want with something like that. (Did not test that)
$scope.scrollTo = function( rowIndex ) {
$scope.gridApi.core.scrollTo( $scope.gridOptions.data[rowIndex], 0);
};
$scope.nextRow() {
var current = $scope.gridApi.selection.getSelectedRows();
scrollTo(current + 1);
}
Cheers

JavaScript validation, prevent duplicate input fields

I have a form with 10 Select Lists all have the same items. The items are populated from a PHP/MySQL array. The user needs to select one item per select list. I need to prevent the user from selecting the same item twice before submitting the form.
function checkDropdowns(){
var iDropdowns = 10;
var sValue;
var aValues = new Array();
var iKey = 0;
for(var i = 1; i <= iDropdowns; ++i){
sValue = document.getElementById('test' + i).value;
if ( !inArray(sValue, aValues) ){
aValues[iKey++] = sValue;
}else{
alert('Duplicate!');
return false;
}
}
return true;
}
Use javascript to add an event listener on the value change of the selects. That function would then loop through the selects taking the values into memory after having compared it to the values it had already. If the loop finds that the current select has an option that is already selected, put it back to default value and display a little message...
Or, still on a change event, take the value of the just selected item and remove all the items of this value in the 10 selects. So at the end the user will only have 1 choice, since he only sees the options he can choose. But be careful, if the user changes his mind on one select, make sure you add back the option you removed in the first place.
Option 2 is to be prefered as a user point of view, you will cause less frustration.
EDIT:
The code you are providing already does quite a lot... All you need now is something to revert the change if it is invalid:
var defaultValues = [];
function onLoadSelect(){//Please execute this on document load, or any event when the select are made available.
var iDropdowns = 10;
var iKey = 0;
for(var i = 1; i <= iDropdowns; ++i){
var sValue = document.getElementById('test' + i).value;
defaultValues['test' + i] = sValue;
}
}
Then, in your function's else, reset the value according to the defaults we have gathered:
else{
alert('Duplicate!');
document.getElementById('test' + i).value = defaultValues['test' + i];
return false;
}
I have written code, i think it can be improved but it works as you asked.
Put it in inside script tag under body so it loads after document.
Put id names of select/dropdown elements in id array.
Take a look: //took me 3 hours O_O
http://jsfiddle.net/techsin/TK9aX/15/
i think i need better strategy to approach programming.

Changing If - Else function to return multiple values?

So the JavaScript code below is what I am using to pull data from our automated marketing software Eloqua. This works for single-select drop downs. What I want it to do is work for multi-select drop downs.
I know that the ProductValue variable works. So with that said I am positive that it is in the if(ProductValue == ProductList[i].value) specifically the " .value " since this is calling the value on in the drop-downs. Is there a way to make this multiple? This has been driving me nuts for days.
function CustomerInformation()
{
var ProductValue = "<span class=eloquaemail>MarketingCustomerInformation1</span>"; //Field Merge...Field merge is working
var ProductList = document.getElementById('C_Marketing_Customer_Information1').options; //Calling the contact record field
for(var i=0; i< ProductList.length; i++)
{
if(ProductValue == ProductList[i].value)
{
document.getElementById('C_Marketing_Customer_Information1').value = ProductValue;
break;
}
else
{
document.getElementById('C_Marketing_Customer_Information1').value = "Select";
}
}
}
you could use multiple for loops:
var ProductValue = "<span class=eloquaemail>MarketingCustomerInformation1</span>"; //Field Merge...Field merge is working
var ProductList = document.getElementById('C_Marketing_Customer_Information1').options; //Calling the contact record field
for(var i=0; i< ProductList.length; i++)
{
for(var j=0;j<ProductValue.length;j++)
{
if(ProductValue[j] == ProductList[i].value)
{
document.getElementById('C_Marketing_Customer_Information1').option[i].selected="selected";
}
}
}
Could also add a boolean var to select a default for when nothing gets selected.
BASIC IDEA
The first loop will go through all the values in the multi-select list.
The second loop will go through all the values you pass it (meaning ProductValue will need to be an array of all the values you want selected).
IF the current item in the first array (ProductList[i]) is equal to (==) the current item in the second array (ProductValue[j]) THEN you will mark the option as selected.
Some useful tools for your JS needs:
google
W3Schools.com
Firebug (Firefox addon, though most browsers have something tied to F12 that can help, use it to see your variables and step through the functions)

Why this javascript is not working in IE?

I am using the following javascript to dynamically add rows in a table:-
var trObj = document.createElement('tr');
trObj.setAttribute('name', 'dynamicTR');
var tdObjEmpty = document.createElement('td');
tdObjEmpty.setAttribute('colspan', '2');
tdObjEmpty.innerHTML = ' '
trObj.appendChild ( tdObjEmpty );
var tdObj = document.createElement('td');
tdObj.setAttribute('colspan', '15');
tdObj.innerHTML = postingDivObj.innerHTML; // <-- copy the innerHTML
trObj.appendChild ( tdObj );
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
targetElementObj = getNthTr ( parentObj, rowIndex1 - extraTr ); // <-- it will just return the trObject,
if ( targetElementObj ){
parentObj.insertBefore(trObj, targetElementObj.nextSibling )
}else{
//alert ( 'targetElementObj is null' );
}
This is working in FF as well as in IE, [ but, i guess, in case of IE name and colspan attribute is not set using setAttribute. but not sure ] .
Now, when i have to remove all rows which are dynamically created i use:-
dynamicTRObjs = document.getElementsByName('dynamicTR');
if ( dynamicTRObjs ){
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
for ( i = 0 ; i < dynamicTRObjs.length; i++ ){
parentObj.removeChild ( dynamicTRObjs[i] );
extraTr++;
}
}
This code removes all dynamically created TRs. and it works fine in FF, but not in IE.
Also in case of IE dynamicTRObjs.length is always 0,whereas in FF dynamicTRObjs.length it gives correct number of rows. Please tell me what i am missing here.
The HTML4 spec list of attributes lists elements that the name attribute can be set on. Tables and table elements are not on the list. The most obvious option is one of,
Keep references to all TRs you create so you don't have to find them in the DOM
Set a className on your TRs and use selectors to find them
That Firefox uses getElementsByName 'correctly' and IE does not is something others have run into too. I'd just avoid using name here altogether.
http://www.quirksmode.org/dom/w3c_core.html
getElementsByName() is not working well in IE6-8
I would suggest that you some other way of identifying that element if you want cross browser usability.
I know, it's a bit off-topic, but let me give you a small advice on using getElementsByName functionality in a browser. It will not help you to solve the current problem (which is because TR can not have Name attribute ), but it will definitely help you to prevent future problems which you will met.
getElementsByName returns you collection, which always keeps itself up-to-date with the DOM tree. This means, that at the moment when you remove ONE item with removeChild, the SIZE of collection will be decreased. So, if you will removing nodes and keep relying on the length of the collection, not all nodes will be removed.
Check this example of your for loop:
Collection length is 3, Your i var is 0, i < length
You remove child,
collection length is 2, your i var is 1, i < length
you remove child,
collection length is 1 and your i var i 2.
Condition i< length == true that means that for loop will stop, BUT some of the elements will still be presented in the DOM.
Choose any solution you like to fix this, but try to avoid relying on the length of the Collection which is returned by getElementsByTagName.
Good luck
since I'm not the only one to suggest avoidance of low-level DOM manipulation, here's an example: an untested implementation with jquery. not exactly an answer to your question, but a comment would lose the formatting.
var mkTd = function (colspan, html)
{
return $('<td />')
.attr('colspan', colspan)
.html(html)
;
}
var addRow = function (rowNr)
{
var target = $('#approvedDisapprovedTableObj tbody tr:eq('+rowNr+')');
if (!target.length) {
//alert ( 'target is null' );
return;
}
target.after(
$('<tr />')
.addClass('dynamicTR')
.append(mkTd(2, ' ')
.append(mkTd(15, $('#postingDivObj').html()))
);
}
var dropRows = function ()
{
$('.dynamicTR').remove();
}
notice that the expression $('.dynamicTR').remove() achieves the same as your
dynamicTRObjs = document.getElementsByName('dynamicTR');
if ( dynamicTRObjs ){
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
for ( i = 0 ; i < dynamicTRObjs.length; i++ ){
parentObj.removeChild ( dynamicTRObjs[i] );
extraTr++;
}
}
IMO it's obvious that the benefits are huge.

Sorting Divs in jQuery by Custom Sort Order

I'm trying to re-sort the child elements of the tag input by comparing
their category attribute to the category order in the Javascript
variable category_sort_order. Then I need to remove divs whose category attribute
does not appear in category_sort_order.
The expected result should be:
any
product1
product2
download
The code:
<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>
<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>
I really don't even know where to begin with this task but if you could please provide any assistance whatsoever I would be extremely grateful.
I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.
The original plugin is here
Here's a revamp for you question
(function($) {
$.fn.reOrder = function(array) {
return this.each(function() {
if (array) {
for(var i=0; i < array.length; i++)
array[i] = $('div[category="' + array[i] + '"]');
$(this).empty();
for(var i=0; i < array.length; i++)
$(this).append(array[i]);
}
});
}
})(jQuery);
and use like so
var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);
This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.
Just note,
Since there is jQuery 1.3.2 sorting is simple without any plugin like:
$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
//your custom sort function returning -1 or 1
//where a , b are $('#input div') elements
}
This will sort all div that are childs of element with id="input" .
Here is how to do it. I used this SO question as a reference.
I tested this code and it works properly for your example:
$(document).ready(function() {
var categories = new Array();
var content = new Array();
//Get Divs
$('#input > [category]').each(function(i) {
//Add to local array
categories[i] = $(this).attr('category');
content[i] = $(this).html();
});
$('#input').empty();
//Sort Divs
var category_sort_order = ['any', 'product', 'download'];
for(i = 0; i < category_sort_order.length; i++) {
//Grab all divs in this category and add them back to the form
for(j = 0; j < categories.length; j++) {
if(categories[j] == category_sort_order[i]) {
$('#input').append('<div category="' +
category_sort_order[i] + '">'
+ content[j] + '</div>');
}
};
}
});
How it works
First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.
The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.
Next it clears out all the divs under the input div.
Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.
The For loop section
#eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.
The first line:
for(i = 0; i < category_sort_order.length; i++) {
Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:
Create a number variable called i and set it equal to zero
If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
When the brackets finish, add one to the variable i (i++ means add one)
Then it repeats step two and three until i is finally bigger than the number of categories in that array.
A.K.A whatever is in the brackets will be run once for every category.
Moving on... for each category, another loop is called. This one:
for(j = 0; j < categories.length; j++) {
loops through all of the categories of the divs that we just deleted from the screen.
Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.
Appending (or prepending) the DOM nodes again will actually sort them in the order you want.
Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.
$(['any', 'product', 'video'])
.map(function(index, category)
{
return $('[category='+category+']');
})
.prependTo('#input');
Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:
// Create a jQuery from our array of category names,
// it won't be usable in the DOM but still some
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
.map(function(index, category)
{
// Here we need to do .get() to return an array of DOM nodes
return $('[category='+category+']').get();
});
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);
// The trick here is that DOM nodes are selected
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.
You can view the test page on jsbin here: http://jsbin.com/ocuta
function compare(x, y, context){
if($.inArray(x, context) > $.inArray(y, context)) return 1;
}
function dom_sort(selector, order_list) {
$items = $(selector);
var dirty = false;
for(var i = 0; i < ($items.length - 1); i++) {
if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
dirty = true;
$items.eq(i).before($items.eq(i+1).remove());
}
}
if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0);
};
dom_sort('#input div[category]', category_sort_order);
Note that the setTimeout might not be necessary, but it just feels safer. Your call.
You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.
It's seems fairly direct to use the sort method for this one:
var category_sort_order = ['any', 'product', 'download'];
// select your categories
$('#input > div')
// filter the selection down to wanted items
.filter(function(){
// get the categories index in the sort order list ("weight")
var w = $.inArray( $(this).attr('category'), category_sort_order );
// in the sort order list?
if ( w > -1 ) {
// this item should be sorted, we'll store it's sorting index, and keep it
$( this ).data( 'sortindex', w );
return true;
}
else {
// remove the item from the DOM and the selection
$( this ).remove();
return false;
}
})
// sort the remainder of the items
.sort(function(a, b){
// use the previously defined values to compare who goes first
return $( a ).data( 'sortindex' ) -
$( b ).data( 'sortindex' );
})
// reappend the selection into it's parent node to "apply" it
.appendTo( '#input' );
If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:
jQuery.fn.sort = Array.prototype.sort;

Categories

Resources