jQuery cloning html, update the DOM? - html

I need help of Javascript / jQuery experts to solve the next problem:
---- 1. this Javascript alerts the id of a selected option in a select html tag:
$(function(){
$("#id_productos_list").change(
function(){
var op = $(this).selectedValues()
alert(op);
}
);
});
----2. this Javascript clone html code:
function cloneMore(selector, type) {
var newElement = $(selector).clone();
var total = $('#id_' + type + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
});
newElement.find('label').each(function() {
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
$(this).attr('for', newFor);
});
total++;
$('#id_' + type + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
}
---- this is a part of the HTML code that JS is cloning, and it works with no problems
<select id="id_productos_list" name="productos_list" >
<option value="1">One</option>
<option value="2">Two</option>
</select>
BUT just the Javascript #1 works with the initial html code (original for clone). the cloned others doesn't alert selected options. I've tested with different id's attrs for each cloned select tags, without results.
What am I missing? Firebug display the cloned html DOM nice :S Do I have to update DOM after cloning to make $("#id productos list").change(...) works?

Have you tried .clone(true) which clones all the handlers attached? It's described at the bottom of the Clone documentation.

The jQuery $("#...") syntax will return the first matched element by exact id. If you are cloning elements but not differentiating them by their id, this code will not work as you expect it to.
You can compare the differences between the following two expressions:
alert($("#id_productos_list").size());
...and
alert($("[id='#id_productos_list']").size());
The first expression will return either zero or one depending on the presence of an element with id "id_productos_list" in your page. The first element in declared order wins.
The second expression will return zero or one or more depending on the the set of all elements with id "id_productos_list" in your page.
Also important to note is that it doesn't appear that the event handlers are copied over as part of the clone() operation. You may need to reassign these handlers to the new elements.
var newElement = $(selector).clone(true);

Related

Value Calculation issue in Google web HTML App

I have created an HTML web app in google script this works like a calculator, This app works fine if I add the input in descending order however if I skip the order and update in put data numbers randomly in any column then I am not getting the output properly
Example:- update the numbers in box number 4 and 5 then update in box number 1 you will find the differences in total numbers
Please refer the attached sheet for detailed script
Project Name- Project Proposal Form
$("#rTpe1").keyup(function(e){
$("#rFor1").val(this.value * $("#PerHourRate1").val());
$("#rFor3").val( Number($("#rFor1").val()) +Number($("#rFor2").val()))
});
$("#rTpe2").keyup(function(e){
$("#rFor2").val(this.value * $("#PerHourRate2").val());
$("#rFor3").val( Number($("#rFor1").val()) + Number($("#rFor2").val()))
});
$("#rTpe12").keyup(function(e){
$("#rFor12").val(this.value * $("#PerHourRate3").val());
$("#rFor3").val( Number($("#rFor1").val()) + Number($("#rFor2").val())+ Number($("#rFor12").val()))
});
$("#rTpe13").keyup(function(e){
$("#rFor13").val(this.value * $("#PerHourRate4").val());
$("#rFor3").val( Number($("#rFor1").val()) + Number($("#rFor2").val())+ Number($("#rFor12").val())+ Number($("#rFor13").val()))
});
I could be wrong, but I think that's the main culprit:
If your work your way top to bottom, the output in '#rFor3' is not affected. For example, if you enter values in the first field ('#rTpe1'), this statement
Number($("#rFor2").val()))
will evaluate to '0' because '#rFor2' probably contains an empty string at this point and Number("") will get you a zero. Because all subsequent input fields reference the results of previous calculations ('rTpe2' references 'rFor1', 'rTpe12' references both 'rFor1' and 'rFor2', etc), the sum will come out as correct.
Now consider the reverse scenario. For simplicity, let's make all your rates equal to 1. If you enter the value of '5' into 'rTpe12', the value of 'rFor3' will be
Number("") + Number("") + Number(5*1) == 5; //the first two inputs will contain empty strings at this point
The output of '#rFor3' would be 5. If you go up a step and enter the value of '2' into 'rTpe2', the value of the 'rFor3' output will change to
Number("") + Number(2*1) == 2; the first input will contain an empty string.
The code is not easy to understand, so even if this solution doesn't work for you, consider caching your DOM elements to improve performance and make your code more readable. Currently, you are using jQuery selectors to search the DOM over and over again, which is a serious performance drag. You could also store your calculated value as a variable and simply add values to it instead of recalculating on each input. For example
$('document').ready(function(){
var total = 0;
var input1 = $('#input1');
var input2 = $('#input1');
var input3 = $('#input1');
var output = $('#output');
input1.keyup(function(e){
var value = Number(this.value);
sum += value;
output.val(sum);
});
});

How to programmatically reorder a column in primefaces datatable

I have a datatable with 10 columns which are grouped in 4:4:2 manner. Now the first two groups(of 4) are fixed while the last 2 can be added to any of the groups(single or both at a time) based on a condition.
Is there a way to set indexes for columns so that they can be ordered based on a condition ?
I see that Primefaces reorder using drag n drop gives the kind of result Im looking for except I want the reordering to be programmable and set before the table is displayed.
I had basically the same problem: after allowing the user to rearrange columns, it is easy to save the arrangement into a cookie, but how to restore the order a day later?
As far as I know, there are no primefaces method to this but can be hacked with a little javascript. (I tried with primefaces 6.0)
1) Give all the columns unique styleClasses, like:
styleClass="mystyle col0"
styleClass="mystyle col1"
styleClass="mystyle col2"
...
2) Convert the styleClass strings into valid css selectors, and store the required order of columns as an order of those, like:
".mystyle.col2,.mystyle.col0,.mystyle.col1"
Put it into a cookie, or store and get back as you like.
3) Write a javascript function to rearrange the cells into the required order:
function reorder() {
var o = getCookie('orderString');
var a = o.split(",");
for (var i = a.length - 1; i >= 0; i--) {
$(a[i]).each(function() {
$(this).prependTo($(this).parent())
});
}
}
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length === 2)
return parts.pop().split(";").shift();
}
(Don't use EL expressions to get back the order string from a backing bean, because it evaluates on page render, so won't update if the user rearranges the columns again.)
4) Execute reorder() "every time you need it".
You'll certainly need it on page loads:
$(document).ready(function() {
reorder();
});
but also on paging events, so add something like this:
<p:ajax event="page" oncomplete="reorder();" />
(And perhaps other times I've not met yet.)

how to update html page only when database changes

JS, which returns the first name:
$.ajax({
url: 'exm1.php',
type: 'get',
dataType: 'json',
success: function(data){
if( t<data.length){
for(var i=0;i<data.length;i++){
$("#output").html(data[i].fn);
}
}
},
error: function() {
// TODO: do error handling here
console.log('An error has occurred while fetching lat/lon.');
}
});
There are two things which I would like to sort out first:
It always overwrites the output div tag. I don't know how I can prevent this
setInterval should only run when there is a change in the database, or maybe when data.length changes, is there any way I could store the previous value of data.length and then compare with new data.length
If #output contains the first name, then you can target and save it into a variable on load
Example:
var firstname = $('#output').html();
And then all you have to do is compare the AJAX output with the variable.
Example:
if ( firstname != data )
$('#output').html(firstname);
To prevent overwriting your div tag, use the following.
$("#output").append(data[i].fn);
I don't understand what you mean by setinterval, please clarify in your code.
You can keep the html inside of the div by using this (vanilla javascript)
var inTheOutPutDiv = document.getElementById("output");
inputTheOutPutDiv.html = inputTheOutPutDiv.html + data; // whatever
// or use inputTheOutPutDiv.html+= data;
but sense you are using Jquery you can use append
$("#output").append(data);
You can instantiate a variable outside of your function that holds the length of your database query (scope). Or you can return a database value that says if the info has been updated (a value of 1 if changed, a value of 0 if the same). Then use a conditional statement to perform an action on the response. You have many options.
Unfortunately you can't call setInterval only when the data changes, but you can run something at a set interval and only update the #output html if the result is different.
The reason #output is getting overwritten is because calling $.html("...") will replace the existing html with the argument. I'd recommend simply putting what you want to remain static on the page in a different div.
<div id='oldOutput'>
Static Text Here: firstName?
<span id='output'></span>
</div>
Now your ajax return will overwrite the element and your static text will remain. However, you still have an issue in that every iteration of your loop will remove the string from before. If you want each loop to render cleanly, i'd recommend something along these lines:
success: function(data){
if( t<data.length){
// if you want to flush the #output each time
$("#output").html("");
for(var i=0;i<data.length;i++){
$("#output").append(data[i].fn);
// or you could put each entry in a div for later use
// $("#output").append("<div id='div_"+ i +"'>" + data[i].fn + "</div>");
}
}
}
In this case (you're flushing the div each time) then if the data is the same you shouldn't notice it update. If you don't flush the div then the data will keep appending on to the end.
If this is what you want, but only when there is new data, then you can count the number of child divs in the #output span - if you wrap each item from the output in a div as I have above, you should be able to compare this to data.length:
success: function(data){
if (t<data.length){
// gets direction descendants of #output that are divs,
// and counts the length of the array returned
var numItems = $("#output > div").length;
if(numItems != data.length){
loop goes here..
}
}
This will sort of work, it will only update when the number of items returned is different to the number of items you already have. However, if your script is returning a set of data and you need to filter it to see what's old and what's new, it's slightly different. You'll need to do include some sort of identifier (other than the data itself) to embed into the to check if it exists later.
if ( $("[data-id=" + data[i].identifier +"]").length > 0 ) {
// already exists, do nothing
} else {
$("#output").append("<div data-id='"+ data[i].identifier +"' etc...
}

Search HTML5 Datalist by Value and inner text (or some other property)

I am attempting to find a way so that when a user enters text into the data list, they can come across the same entry by course number (E.G. "CS 101") or course name (E.G. "Intro to Computer Science).
Currently, what I have is only searchable by the value field:
<datalist id="tagList">
<option value=""></option>
<option value="CSCI 4950">Senior Software Project</option>
<option value="CSCI 5117">Developing the Interactive Web</option>
<option value="CSCI 5421">Advanced Algorithms</option>
<option value="CSCI 5980">Design Methods for Comp. Sci.</option>
</datalist>
The solution needs to work in the Android Webkit web browser (Phonegap) -- Chrome seems to handle Datalists the same as Android's native browser so if it works in Chrome I should be ok.
It needs to display both the course name and course number to the user
This needs to be generalizable and not hard-coded as I am using AngularJS to actually populate the full list of courses.
What I've tried
https://stackoverflow.com/a/22827978/2831961 -- For some reason, this didn't work.
I've also tried a similar strategy, but with the data-value attribute. That didn't work either. Unless I am responsible for some behind the scenes Javascript work that I am unaware of.
http://jsfiddle.net/rh48cgrj/3/
Here's a fiddle. I put the option values/text into key:value pairs in a javascript object. NOTE: the key is an index number and the value is the option value attribute AND the text. This makes it easier to search them for our text.
var i = 0;
var selectItems = {}
$('#tagList option').each(function() {
var listvalue = $(this).val();
var listtext = $(this).text();
selectItems[i] = listvalue + " " + listtext + ",";
i++;
});
Then I split them into rows that included both value and text.
count = i;
for(i=0; i < count;i++) {
var blockoftext = blockoftext + " " + selectItems[i].toLowerCase() + ",";
}
I then setup a search function that would search those rows to see if any returned a match, and if they did the result was outputted to a div below the search box.
var texttosplit = blockoftext.split(",");
var searchresults;
for(i=0; i < texttosplit.length; i++) {
(texttosplit[i].indexOf(searchvalue.toLowerCase()) != -1) ?
(searchresults = texttosplit[i] + "<br>") : false;
$("#searched").html(searchresults);
}
There's an example for all of the above in the fiddle.
EDIT: The below is the commented code for the loop to check if search text is in the datalist per op request.
for (i = 0; i < texttosplit.length; i++) {
//The above loops through our array of class values and titles
(texttosplit[i].indexOf(searchvalue.toLowerCase()) != -1) ?
// The above determines if our search text is in class title using a ternary operator
// our array of class values and titles is lowercase so we make
//sure our search text is lowercase as well
// if we find a match between the search text and the class title/values perform the following:
(searchresults = texttosplit[i].replace(/\b[a-z]/g, function(letter) {
return letter.toUpperCase();
})
// The above replaces the first char of every word with an uppercase char
.replace("Csci", "CSCI") + "<br>",
// The above finds Csci and changes it to CSCI since all THOSE letters should be uppercase
prevtext = $("#searched").html(),
//get current text of element with id "searched" and place it in prevtext
$("#searched").html(prevtext + searchresults))
//append "searched" by adding it's current text with the new searchresults
:
//if search text is not in the class title return false
false;
}

IE8 select population performance issues - solutions needed

I have an application that is having issue when populating selects with over 100 items. This problem only occurs in IE8. I am using angularjs to do the population, but my research shows that this is a general problem with IE8. What solutions have others used to deal with this problem. We have over 40,000 users tied to IE8 for the foreseeable future (Fortune 200 company) so moving to another browser is not an option.
Some thoughts I had.
Create a series of option tags as a one long string in memory and replace the innerHTML of the . But running some people samples this does not appear to solve the issue.
Originally populating the select with a few and then adding the rest as the user scrolls down. I am not sure if this is possible, or how to implement this
I am sure others have run into this issue. Does anyone have some ideas?
Thanks,
Jerry
Another solution that preserves the original <select> is to set the <option> values after adding the options to the <select>.
Theory
Add the <option> elements to a document fragment.
Add the document fragment to the <select>.
Set the value for each <option>.
Practice
In practice we end up with a couple issues we have to work around to get this to work:
IE11 is very slow when setting the value for each individual <option>.
IE8 has selection bugs because it isn't properly doing a re-flow/layout on the <select>.
Result
To handle these what we really do is something like the following:
Add the <option> tags to a document fragment. Make sure to set the values so that step 3 is a no-op in IE11.
Add the document fragment to the <select>.
Set the value for each <option>. In IE8 this will set the values, in IE11 this is a no-op.
In a setTimeout add and remove a dummy <option>. This forces a re-flow.
Code
function setSelectOptions(select, options)
{
select.innerHTML = ''; // Blank the list.
// 1. Add the options to a document fragment.
var docFrag = document.createDocumentFragement();
for (var i = 0; i < options.length; i++)
{
var opt = document.createElement('option');
opt.text = options[i];
docFrag.appendChild(opt);
}
// 2. Add the document fragment to the select.
select.appendChild(docFrag);
// 3. Set the option values for IE8. This is a no-op in IE11.
for (i = 0; i < options.length; i++)
select.options[i].text = options[i];
// 4. Force a re-flow/layout to fix IE8 selection bugs.
window.setTimeout(function()
{
select.add(document.createElement('option'));
select.remove(select.options.length - 1);
}, 0);
}
The best solution seems to be to create the Select and it's options as a text string and add that string as the innerHTML of the containing tag such as a DIV. Below is some code.
<div id="selectHome" ></div>
In JS (from angular controller)
function insertSelect(divForSelect) {
var str = "<select id='myselect'>";
for (var i = 0; i < data.length; i++) {
str += '<option>' + data[i] + '</data>';
}
str += '</select>';
divForSelect.innnerHTML = str;
}
Note that inserting options into a existing Select is very slow (8,000 msecs for 2000 items). But, if the select and the options are inserted as a single string it is very fast (12 msec for 2000 items).