I have an XML file (consisting of a few data items, namely <Book> ) and a corresponding XSLT file that I created, which when opened up in a browser turns this list of books into an html table. The columns are named "Author", "Title", and "BookID" (they are ids of child nodes of <tr>).
Now I want to make the resulting page dynamic using jQuery, i.e. I want to make the resulting table rows to be sorted on the column I click on. However, while the table renders fine, the resulting jQuery code seems to have no effect.
I am not sure whether this is a result of bugs in my jQuery code, or whether I didn't include it properly, or both. I included two <script></script> tags in my XSL file (one to hotlink the jQuery library, the other to link to my code), but I'm not sure if this is the correct way to do it. also, can someone look over my jQuery code to find out if there's anything wrong (I'm a complete newbie to web programming, so please forgive my errors)?
Thanks!
$(document).ready(function() {
function sortBy(a, b, selectFunction) {
var a1 = selectFunction(a);
var b1 = selectFunction(b);
if (a1 < b1) {
return -1;
}
if (a1 > b1) {
return 1;
}
return 0;
}
function sortHelper(index) {
var rows = $('table tbody tr').get();
rows.sort(function(a, b) {
return sortBy(a, b, function(x) { return $(x).children('td').eq(index).text().toUpperCase(); });
});
rows.appendTo('tbody');
}
$('#Author').click(function() {
sortHelper(0);
});
$('#Title').click(function() {
sortHelper(1);
});
$('#BookID').click(function() {
sortHelper(2);
});
});
A stated in the comments .get() returns a javascript object. So to use rows.sort() you want the jQuery object.
// javascript
$(obj).get(0); // returns the first element from the query
// jquery
$(obj).eq(0); // return the first $(element) from the query.
Also one thing I noticed:
since you're accessing the td's by an id you can do something like:
var topRow = $("table tbody tr").eq(0),
topCells = topRow.find("td"); // expecting #author, #title, #bookid
topCells.click(function(){
sortHelper($(this).index()); // makes more sense this way
});
Other than that if you're loading external *.js files into your solution you'll be fine. If you're inserting code directly into the page use CDATA encoding as described here.
Related
I have a list being generated from ng-repeat and it's rendering a component tag on each iteration. And I'm giving each iteration a unique id utilizing the $index value.
That looks like this
<div ng-if="$ctrl.myArr.length > 0" ng-repeat="obj in $ctrl.myArr">
<myCustomComponentTag id="L1-{{$index}}" obj="obj"></myCustomComponentTag>
</div>
I need to run some jquery on these unique ids, which are populating just fine. The view will successfully show the tags to have ids of #L1-0, #L1-1, #L1-2
The snag is that my function running my jquery is executing before the view fully loads and the id values actually populate with the value of $index
My jQuery is searching for $(`#L1-${i}`) in a loop. If I store #L1-${i} in a string variable and output its value it will return '#L1-0'.
But '#L1-0' does not exist yet, as the view has not been fully populated. When I break on my function the ids on the elements only read L1-{{$index}} and have yet to populate.
I've read in several places to try this directive. This is still not working. It seems that just because my ng-repeat has reached its last element, it does not mean the view has populated and my ids are fully loaded and in place.
How can I execute my jQuery function only after the view is fully populated with my data?
Edit:
This is the jQuery function
jqFn= () => {
if (this.myArr.length > 0) {
for (var i: number = 0; i < this.myArr.length; i++) {
$(`#L1-${i}`).css({
/*
Add various styles here
*/
});
}
}
};
Perhaps you can try something like this
Disclaimer: I have found it one of the comments on the link that you referenced in the post.
The solution I used for this was a recursive function that set a timeout and searched for the id of the last object in the repeat statement until it exuisted. The id could not exist until the dynamic data from the $watch fully loaded in. This guaranteed all my data was accessible.
Code looked similar to this
setListener = (el, cb) => {
if ($(el).length) {
cb();
} else {
setTimeout(() => {
this.setListener(el, cb);
}, 500);
}
};
I am using DataTables.
What I am trying to do is: by using one of the columns values, get page number, where this value is located.
I have tried this: jumpToData()
BUT this didn't work out. The reason is that
var pos = this.column(column, { order: 'current' }).data().indexOf(data);
in jQuery.fn.dataTable.Api.register('page.jumpToData()' returns value >=0 ONLY if I was placed on page where value was.
For example, I want to detect page where needed value is, but I am staying on another page, so to detect value on... page 3, I need to go to this page and only then I can detect it, which makes no sence at all.
What I need to do, is: by staying on pirst page, using value from another pages, detect those pages numbers and then navigate to them:
$('#Grid_grid').DataTable().page(PageNumber).draw(false);
How can I accomplish that?
EDIT:
Got some idea (several changes in jumpToData()):
jQuery.fn.dataTable.Api.register('page.jumpToData()', function (data, column) {
for (var i = 0; i < this.page.info().pages; i++) {
var test = this.page(i).column(column, { order: 'current' }).data().indexOf(data);
if (test >= 0) {
this.page(i).draw(false);
return this;
}
}
return this;
});
(EDIT 2: idea didn't paid off, no difference)
BUT now I got second issue:
None methods of datatable works in .cshtml page.
For example I need to get overall page count. I doing this:
$('#Grid_grid').DataTable().page.info().pages;
and this return me 0;
Meanwhile, putting it in to console (Chrome F12) works fine (returns 5). Whats the matter?
EDIT 3:
Came up with this:
function LoadPage(value) {
var table = $('#Grid_grid').DataTable();
var pageNumber = table.search(value).page();
table.page(pageNumber).draw(false);
}
Looks promising BUT, I still cant validate it because in console DataTable methods are working, but in .cshtml no. (search() or page() returns nothing).
EDIT 4:
Moved issue to another question
CAUSE
Your new API method page.jumpToData() tries to query all pages data because second argument selector-modifier in column() API method has property page: 'all' by default. As written it will always stay on first page.
SOLUTION
There is original page.jumpToData() plug-in posted by Allan Jardine, creator of DataTables. It works as intended and can be used instead of your modification to avoid unnecessary iterations.
$.fn.dataTable.Api.register('page.jumpToData()', function (data, column) {
var pos = this.column(column, {
order: 'current'
}).data().indexOf(data);
if (pos >= 0) {
var page = Math.floor(pos / this.page.info().length);
this.page(page).draw(false);
}
return this;
});
DEMO
See this jsFiddle for code and demonstration.
NOTES
In the demo above I added console.log("Number of pages", table.page.info().pages); just to demonstrate that API method works. However they may work because I have HTML-sourced data.
If you have Ajax-sourced data, you need to query number of pages only when data has been loaded. Use initComplete option to define a callback function that will be called when your table has fully been initialised, data loaded and drawn.
I'll try to explain my problem with few words.
I have an HTML with various iframes. In one iframe there is a Table of Contents (TOC) and in an other the content of the corresponding element highlighted in the TOC. Since there are various TOCs, it might happen that by clicking on a link, we'll be taken to a topic which belongs to another TOC, and therefore I want the TOC frame to be reloaded with the proper TOC. To do so, since each topic has a unique id within the TOC, I perform a search of the id of the topic loaded in the main frame accross all the TOCs and when I find the wanted TOC, I load it in the TOC frame.
The code I've written so far is the seguent:
/*function called on load of each topic - it gets the topic unique id as parameter*/
function highlight(id) {
/*the names of the HTML files containing the different tocs*/
var tocs = ["toc.htm", "toc_documentazione.htm", "toc_flussiesteri.htm", "toc_garante.htm", "toc_legittimita.htm", "toc_normativa.htm", "toc_settori.htm", "toc_sicurezza.htm", "toc_sistemadp.htm", "toc_vistaarticolato.htm"]
var i = 0;
/*search within the different TOCs until you find a correspondence or there are no more TOCs*/
while (!changeTOC(tocs[i], "a" + id) && i < tocs.length) {
i = i + 1;
}
/*this line is probably wrong but the idea is to load the found TOC in the appropriate frame*/
$(content).load(tocs[i - 1] + " #content");
}
/*function using ajax to search the id into the HTML file passed as parameter (newToc) returning the search outcome*/
function changeTOC(newToc, id) {
var found = false;
$.get(newToc, "html").done(
function(temp_toc) {
/*if the HTML contains the id we search for we return true*/
if (temp_toc.indexOf(id) != -1)
found = true;
});
/*else we return false*/
return found;
}
The problem I have is with the while cycle I use to search through the various TOC files. I did some debugging and, regardless from the fact the TOC containing the id I'm searching for is at the first position, the while extecutes 10 cycles and only at the very last it tells me that it has found the matching TOC, which is indeed the first one in the list.
Hope I've been able to make myself clear.
Thanks for your help
I finally managed to get this done by using the ajax call with syncronus set to true. I'm not posting here all the code cause it would be confusing, but below is what I changed compared to the code written above and now everything works just fine. Maybe it's not optimal in terms of performance but I don't have this concern, so I'm happy with it :)
Hope this can help others.
/*function called on load of each topic - it gets the topic unique id as parameter*/
function highlight(id) {
var tmpVal = sessionStorage.getItem('key');
//we check if there is another element in the TOC currently highlighted in bold, if so we remove the highlight
if(tmpVal) {
var tmpEle=parent.window.frames[0].document.getElementById('a'+tmpVal);
if (tmpEle) {
tmpEle.className='';
}
}
//loop through all TOCs to find the one containing the selected topic
var tocs = ["toc.htm","toc_documentazione.htm","toc_flussiesteri.htm","toc_garante.htm","toc_legittimita.htm","toc_normativa.htm","toc_settori.htm","toc_sicurezza.htm","toc_sistemadp.htm","toc_vistaarticolato.htm"];
var i=0;
while (!changeTOC(tocs[i],"a"+id)&&i<tocs.length){
i=i+1;
}
//get currently loaded TOC
var currentToc=$("#toc_iframe",parent.document).attr("src");
var indexCurrentTOC=tocs.indexOf(currentToc);
//we check if the matching TOC is the current one, if so we don't change anything
if(!changeTOC(tocs[indexCurrentTOC],"a"+id)){
$("#toc_iframe",parent.document).attr("src",tocs[i]);
}
var myElt=parent.window.frames[0].document.getElementById('a'+id);
//highlight current element in the TOC
myElt.focus();
myElt.className+=' active';
scrollTo(myElt.offsetLeft-48, myElt.offsetTop-(parent.document.body.clientHeight/3));
sessionStorage.setItem("key", id);
}
//searches for the element with given id into the toc file newToc
function changeTOC(newToc,id){
var found = false;
$.ajax({
url: newToc,
async: false,
context: document.body
}).done(function(temp_toc) {
if(temp_toc.indexOf(id)!=-1){
found = true;
}
});
return found;
}
Context:
I work a student job transcribing paper reports in a webapp. It's old and we unfortunately can't change the source nor directly run a DB query.
It only checks if the unique ID exists once you submit the entire form, and you can't submit it unless it's entirely filled. Needless to say, it's a huge waste of time as you often transcribe the whole thing only to realise it's a duplicate.
Objective:
I made the userscript below that launches a search the search on the onblur of the unique ID's input(noReferenceDeclarant), checks if there are any matches (rows) and returns accordingly. Runs with Greasemonkey. The search form is in another page on the same domain. The search form does not take any URL arguments.
Can this be done without using an iframe (AJAX perhaps?)
This is a tool for my own productivity & to learn JS at the same time. As I'm still very much a beginner, any tips to make that code cleaner are welcome.
//Adding function to input's blur event
$(document).on ("blur", "#noReferenceDeclarant", isRefNumberExists);
//Vars
var noReferenceDeclarant = '';
var loadCode = 0;
var $searchForm;
//Fonctions
function isRefNumberExists ()
{
noReferenceDeclarant = $('#noReferenceDeclarant').val();
loadCode = 0;
//Make sure there's data in the input before proceeding
if (noReferenceDeclarant)
{
//Build search iframe
$searchForm = $('<iframe />', {
name: 'searchWindow',
src: 'rechercherGriIntranet.do?methode=presenterRechercher',
id: 'searchWindow',
width: 0,
height: 0
}).appendTo('body');
$searchForm.load(searchRefNumber);
}
}
function searchRefNumber()
{
var isExists = false;
//Check which "load" it is to avoid submit loops
if (loadCode === 0)
{
loadCode = 1;
//Filling search form with search term
$(this.contentDocument).find('#noReference').val(noReferenceDeclarant);
//Set search form preferences
$(this.contentDocument).find('#typeRapportAss').prop('checked', false);
$(this.contentDocument).find('#typeRapportAS').prop('checked', false);
$(this.contentDocument).find('#typeRapportSI').prop('checked', true);
//Submit the form
$(this.contentDocument).find('form:first').submit();
}
else if (loadCode === 1)
{
loadCode = 2;
//See if there are any tr in the result table. If there are no results, there a thead but no tr.
var foundReports = $(this.contentDocument).find('.resultatRecherche tr').length;
if (foundReports > 0)
{
if (confirm('A report matching this ID already exists. Do you want to display it?'))
{
//Modal window loading the report in an iframe. Not done yet but that's fairly straightforward.
}
else
{
//Close and return to the form.
}
}
}
//Reset variables/clean ressources
delete $searchForm;
$('#dateRedactionRapport').focus();
}
On the whole I've seen far, far worse code.
Ajax could do it, but then you'd just have to put the AJAX response into the DOM (as an iframe, most likely).
In this instance, I'd keep the approach you have. I think it is the sanest.j
Without the full context, there may be a way to clean up the loadCode -- but what you have is pretty same and works. A lot of folks would call it a semaphore, but that is just an issue of terminology.
The only thing I"d really clean up is recommend not calling the jQuery object so often..
// Many folks recommend that jQuery variables be named $<something>
var $doc = $(this.contentDocument);
doc.find('#typeRapportAss').prop('checked', false);
$doc.find('#typeRapportAS').prop('checked', false);
$doc.find('#typeRapportSI').prop('checked', true);
If you wanted to play with jQuery data structures, you could make a 'config' object that looks like this:
var formValues = {
typeRapportAs: false,
typeRapportAS: false,
typeRapportSI: true
};
then iterate over that to (using for ... in with .hasOwnProperty).
Not NEEDED for this project, what you are doing is fine, but it might make a learning exercise.
I have a pretty simple HTML form where users can enter in information about a person. Below that form is a button which allows them to 'add more'. When clicked, the 'person' form is copied and appended to the page.
The way I used to do this was to take my HTML file, copy out the relevant section (the part that gets 'added more') and then save it into a variable in the Javascript. This became rather annoying when I had to make changes to the form as I would then have to make the same changes to the Javascript variable.
My new method is to create the variable dynamically in Javascript. When the page loads, I use jQuery to grab out the 'add more' part of the code and cache the HTML into a variable. Then when the 'add more' button is clicked, I append that cached HTML to the page.
The problem is with form inputs. The server-side code autofills the form with the user's data from the database. I want to cache that HTML data with no form inputs...
My current function looks like this:
function getHTML($obj, clean)
{
if (clean)
{
var $html = $obj.clone();
$html.find('input').each(function() { $(this)[0].value = ''; });
}
else
{
var $html = $obj;
}
var html = $html.wrap('<div></div>').parent()[0].innerHTML;
$html.unwrap();
return html;
}
It doesn't work. I'm also unsure if this is the best approach to solving the problem.
Any ideas?
I don't know why this wouldn't work. I can't see how the function is being called, or what is being passed to it.
I guess one thing I'd do differently would be to create a .clone() whether or not you're "cleaning" the inputs. Then you're not wrapping and unwrapping an element that is in the DOM. Just use the if() statement to decide whether or not to clean it.
Something like this:
function getHTML($obj, clean) {
var $clone = $obj.clone();
if (clean) {
$clone.find('input').each(function() { this.value = ''; });
}
return $clone.wrap('<div></div>').parent()[0].innerHTML;
}
Or a little more jQuery and less code:
function getHTML($obj) {
return $obj.clone().find('input').val('').end().wrap('<div/>').parent().html();
}
A little less efficient, but if it only runs once at the page load, then perhaps not a concern.
Or if it is going to be made into a jQuery object eventually anyway, why not just return that?
function getHTML($obj) {
return $obj.clone().find('input').val('').end();
}
Now you've returned a cleaned clone of the original that is ready to be inserted whenever you want.
EDIT:
Can't figure out right now why we can't get a new string.
Here's a function that will return the DOM elements. Beyond that, I'm stumped!
function getHTML($obj, clean) {
var $clone = $obj.clone();
if (clean) {
$clone.find('input').each(function() {
this.value = '';
});
}
return $clone.get(); // Return Array of DOM Elements
}
EDIT: Works now.
I ditched most of the jQuery, and used .setAttribute("value","") instead of this.value.
Give it a try:
function getHTML($obj, clean) {
var clone = $obj[0].cloneNode(true);
var inputs = clone.getElementsByTagName('input');
console.log(inputs);
for(var i = 0, len = inputs.length; i < len; i++) {
inputs[i].setAttribute('value','');
}
return $('<div></div>').append(clone)[0].innerHTML;
}
I would wrap the part of the form that needs to be cloned in a <fieldset>:
<form id="my_form">
<fieldset id="clone_1">
<input name="field_1_1">
<input name="field_2_1">
<input name="field_3_1">
</fieldset>
</form>
Add one more
Then for the jQuery script:
$("#fieldset_clone").click(function(event) {
// Get the number of current clones and set the new count ...
var cloneCount = parseInt($("fieldset[id^=clone_]").size());
var newCloneCount = cloneCount++;
// ... then create new clone based on the first fieldset ...
var newClone = $("#clone_1").clone();
// .. and do the cleanup, make sure it has
// unique IDs and name for server-side parsing
newClone.attr('id', 'clone_' + newCloneCount);
newClone.find("input[id^=clone_]").each(function() {
$(this).val('').attr('name', ($(this).attr('name').substr(0,7)) + newCloneCount);
});
// .. and finally insert it after the last fieldset
newClone.insertAfter("#clone_" + cloneCount);
event.preventDefault();
});
This would not only clone and clean the set of input fields, but it would also set new ID's and names so once the form is posted, their values would not be overwritten by the last set.
Also, in case you want to add the option of removing sets as well (one might add too many by mistake, or whatever other reason), having them wrapped in a <fieldset> that has an unique ID will help in accessing it and doing a .remove() on it.
Hope this helps.