How can you stop scrolling on JqGrid when editing in batch mode - javascript

The JqGrid example at http://www.guriddo.net/demo/guriddojs/edit_add_delete/inline_batch/index.html shows how to use 'batch editing'. However when the edit button is clicked, focus scrolls to the last rows initial edit column. How can that be made to focus on the first row instead? Also how can it be used with paging?

The first requirement can be accomplished if you loop in reverse order, since the ids are get from the first row to the last - i.e. the function startEdit can look like:
function startEdit() {
var grid = $("#jqGrid");
var ids = grid.jqGrid('getDataIDs');
for (var i = ids.length - 1; i >= 0; i--) {
grid.jqGrid('editRow',ids[i]);
}
}
As for the other requirements it depends what should be done when the new page is requested - to save existing edits or cancel them.
For this purpose it is needed to use onPaging and maybe onSortCol (in case of sorting) events, but all depend on the specific requirements above

Related

Set embedded data in Qualtrics with javascript for use with display logic (need updates?)

While this issue has been addressed a few times here and here, subsequent Qualtrics updates have made it difficult to continue to apply these solutions.
My objective: count up the number of text boxes containing responses from 2 questions and use that number to determine what gets displayed in a third. The following javascript code used in a text question type on a page separating the second and third questions works fine:
Qualtrics.SurveyEngine.addOnload(function()
{
//Count up the number of text boxes with anything in them
var count = 0;
if('${q://QID1/ChoiceTextEntryValue/1}') count++;
if('${q://QID1/ChoiceTextEntryValue/2}') count++;
//etc...
if('${q://QID2/ChoiceTextEntryValue/1}') count++;
if('${q://QID2/ChoiceTextEntryValue/2}') count++;
//etc...
//Set count as embedded data (added to survey flow earlier)
Qualtrics.SurveyEngine.setEmbeddedData('count', count);
};
The problem is that I have a useless page between my second and third questions. If I use display logic to hide my intervening question with this code, the code doesn't execute. If I use javascript to hide the question and automatically move to the next page as described here, it works, but then the user can't move back in the survey because jQuery('#NextButton').click(); returns them to the third question. I've tried wrapping the above code in a substitute NextButton code and moving it to the same page with QID2 as shown here with at least one update that I could figure out:
this.hideNextButton ();
$('NextButton').insert({
before: "<input id=\"checkButton\" type=\"button\" value=\" -> \" title=\" -> \">"
});
$('checkButton').onclick = function() {
//Previous count and set code here
$('NextButton').click();
};
This works for counting up everything from QID1 (located on the previous page), but doesn't catch those from QID2 (same page).
I've also tinkered with placing code in addOnReady and addOnUnload to no avail.
What I need is either 1) an update to the solution that hides the question on an intervening page that modifies the Back button on the page with my third question to kick the participant two pages back, or 2) an update to the substitute button solution that will grab counts from the question on the same page.
With the hint given by #T.Gibbons, I was able to get things working.
Code for the first question, after including count1 as embedded data in the Survey Flow:
//Count up the number of text boxes with anything in them
var qid = this.questionId; //Pull question id
//Focus on a text box to force listener to fire; ensures downstream logic will work regardless of participant behavior
document.getElementById('QR~'+qid+'~1').focus();
var quest = document.getElementById(qid); //Set up listener
quest.addEventListener('blur', function() {
var count1 = 0;
if(document.getElementById('QR~'+qid+'~1').value) count1++;
if(document.getElementById('QR~'+qid+'~2').value) count1++;
//and so on...
//Set count1 as embedded data
Qualtrics.SurveyEngine.setEmbeddedData('count1', count1);
}, {capture: true});
Code for the second question, after including count in the Survey Flow:
//Count up the number of text boxes with anything in them, add to prior question
var qid = this.questionId; //Pull question id
var count1 = Qualtrics.SurveyEngine.getEmbeddedData('count1'); //Pull count1 from previous question
document.getElementById('QR~'+qid+'~1').focus(); //Focus on a text box, force listener to fire
//Set up listener
var quest = document.getElementById(qid);
quest.addEventListener('blur', function() {
var count2 = 0;
if(document.getElementById('QR~'+qid+'~1').value) count2++;
if(document.getElementById('QR~'+qid+'~2').value) count2++;
// and so on...
var count = count1 + count2;
//Set count as embedded data
Qualtrics.SurveyEngine.setEmbeddedData('count', count);
}, {capture: true});
Set the survey logic to depend on count and it's good to go.

Is there a jQuery UI "anti-sortable" that will let LI's be taken and from a UL, but always specifying order?

I have something partially working where there are two UL's, both having had jQuery UI .sortable() called on it. The user can and potentially should drop LI's from one to the other. I am looking to have the second list really be sortable, but the first list retain a single ordering instead of having a LI from the second list appended at the end if the user clicks on it.
I see one painfully obvious way to do it: keep a JavaScript list of values of LI's, or alternately set a data-index='0' (then, 1, 2, 3, etc.), and then in either case make a single, possibly bottom-up, sweep of the dread bubble sort.
This appears to me something I could straightforwardly get working, but it has an "If you're doing it this way, you're working too low-level" code smell to me. Apart from a bubble sort reference, in a case where I think O(n) really is tolerable, it seems like something where someone who knew jQuery UI could produce a much shorter and clearer implementation.
I've outlined above the hard way of addressing my problem. What easy ways should I consider instead.
After a little more hesitation, I decided to go with the obvious solution, even if it doesn't smell like a usual jQuery optimal solution. I added an ascending integer data- field to the original LI's in the server-side code that generates them, and client-side created a single bubble sort iteration function that should work if all but the last item (re-added items start out last) are in order:
var get_index = function(node)
{
return parseInt(jQuery(node).attr('data-sort-position'));
};
var sort_available = function()
{
var len = jQuery('#available > li').length;
if (len >= 2)
{
var moved = false;
for(var index = 0; index < len; index += 1)
{
if (!moved)
{
if (get_index(jQuery('#available > li')[index]) >
get_index(jQuery('#available > li')[len - 1]))
{
var node = jQuery(
jQuery('#available > li')[len - 1]).detach();
jQuery(node).insertBefore(
jQuery('#available > li')[index]);
moved = true;
}
}
}
}
};
I then added a sort_available() call to the end of the handler that is called when a member of the other, destination list is clicked:
jQuery('#included li').click(function(event)
{
jQuery(event.target).detach();
jQuery('#available').append(event.target);
assign_clicks();
sort_available();
});
Now it seems to be putting items back in their original places.

Preventing DOMSubtreeModified event in Chrome from firing with every change in JS

everyone. There's an issue with Chrome (I don't know if it happens with other browsers as well). The situation is with a website that is a few years old. Was developped with standard JavaScript, not jQuery. There are thousands of codelines, so updating it now is not an option.
The thing is, this reads data from a database at the server, which returns some times 10 rows, some times over 500. That is the normal operation. In any case, the request is done via simple AJAX, and it returns an XML. The database responds in less than a second, the formatting of the XML is also in a second, maybe two on a slow day. But when JavaScript receives it back, that's the problem. With 10 elements in the XML there's no problem, but when it's 500, I want to kill myself.
After days of debugging and trying to find the problem, I finally did, but don't know how to fix it. The problem is that when the XML returns, every element is displayed in a simple <table>, meaning every node is basically a <tr> and every node's child is a <td> and <input type="text">. In the end, no matter how many rows it has, the table has 7 columns.
So here's an example of what it does. It simply goes through the nodes and creates the rows and cells with their input fields:
var table = document.getElementById("theTable");
for (var i = 0; i < theXML.childNodes.length; i++)
{
var row = table.insertRow(table.rows.length);
row.style.backgroundColor = "Red";
for (var j = 0; j < theXML.childNodes[i].childNodes.length; j++)
{
var cell = row.insertCell(row.cells.length);
cell.width = "200px";
cell.className = "cellClass";
var d = document.createElement("input")
d.type = "text";
d.width = "190px";
cell.appendChild(d);
}
}
Now, the problem
The code does what it's supposed to do, the table is created with the right rows, columns and inputs, but each and every single time it goes through a width, appendChild, className or other attribute setting, Chrome calls a DOMSubtreeModified event, which is first defined in the Startup() function in the content.js file (not part of my project, so it most be Chrome's). The event calls a onDomChange function which receives document as parameter, which only calls updateDocumentListeners and sends it the parameter, which calls updateInputListeners, which goes through EVERY SINGLE INPUT in the document using a for(), just to call updateInputListeners, which only return true or false depending on some conditions.
Now, when there are only 10 rows, that means that in the end we'll have 70 inputs, which in turn means that by the end it would have called the onDomChange several times, that is: 70 + 69 + 68 + ...., because it goes through everything that's been added before. And that was to be also multiplied by every single attribute you set.
In other words, with every input created, it calls onDomChange. On every attribute I set, it calls onDomChange. If I add a second input, it goes twice, once for the element and/or the attribute I'm setting and another for the previous one. Here's what it does:
//This is the code I found while debugging
function updateDocumentListeners(document)
{
var inputs = document.getElementsByTagName('INPUT');
for (var i = 0; i < inputs.length; ++i)
updateInputListeners(inputs[i]);
}
With 500 inputs, meaning also 500 cells, it simply won't end. Oh, and if there are already other inputs (including types hidden, radio, etc), they are included in the cycle. And then Chrome has the audacity to send a message with an option telling me that it's taking too long and prompts for whether I want to wait or cancel.
What I've tried
Since it all happens because of the event, I've tried to remove it, process the table and then reinstate it, but I can't remove it, because it's declared like this in content.js
document.addEventListener('DOMSubtreeModified', function(){ onDomChangeNoThrow(document); }, true);
This means I can't use removeEventListener, because it is defined as an anonymous function. I've already tried it.
Any suggestions?
Thanks.

Handsontable Performance Issues On Updating Comments to Cells

I have a function that updates a comment to every single cell in a row. This function is called many times by a higher level function that loops through every row in the table and determines what comments to apply.
This all works fine. See a simplified version of the code below.
// Loop through all hot rows and determine comment to apply
var loopThroughHotRows = function (hot) {
var rows = hot.getSourceData().length;
for (var i = 0; i < rows; i++) {
var comment = "some comment determined by another function";
applyResponseCommentsToRow(hot, comment, i);
}
}
// Apply comments to a whole row in a passed handsontable
var applyCommentsToRow = function (hot, comment, logicalrow) {
var cols = hot.countCols();
var commentsPlugin = hot.getPlugin('comments');
for (var i = 0; i < cols; i++) {
// render being issued for each comment set.
// need to restrict rendering somehow.
commentsPlugin.setCommentAtCell(logicalrow, i, comment);
}
}
The problem is that each time a comment is applied to a cell. The rendering of the entire handsontable instance is initiated. Causing the web browser to get blocked/chug/become very slow and responsive until all the rendering is complete.
So my question is. Is there some way to prevent Handsontable from rendering each time that a new comment is applied to a cell? Either by temporarily disabling all rendering or adding the comments in a different manner?
I actually ended up figuring out a solution to this on my own. If you set the comment of the cell by calling the hot.getCellMeta() function.
This actually bypasses the re-rendering of the handsontable. See updated function below.
// Apply comments to a whole row in a passed handsontable
var applyCommentsToRow = function (hot, comment, logicalrow) {
var cols = hot.countCols();
for (var i = 0; i < cols; i++) {
// New method of writing comment to cell. Does not cause handsontable re-render.
hot.getCellMeta(logicalrow, i).comment = {'value':responseComments};
}
// Call render once after all comments have been assigned to row!
hot.render();
}
The first thing I can think of to enhance the speed of your function is to not change the comment in cells when it's not necessary. (old comment value = new comment value). To do that, you simply have to compare both String before doing setCommentAtCell :
if(comment.localeCompare(hot.getPlugin('comments').getCommentAtCell(logicalRow,i)) != 0)
commentsPlugin.setCommentAtCell(logicalRow, i, comment);
I used a little example to quickly test this change that you can find in this JSFiddle. (For the sake of 'quick testing', I trigger the change comment for every cell when I copy : whether you use ctrl+C in the table, or you use the action copy in the context menu).
You will see that it will freeze the first time (as every cell will be modified), but the second time there is no freeze at all since the changes are not necessary.

Inject divs with jQuery while keeping word distance from specific elements

I need to inject some divs (that contain links to other articles in the site) in the content of a page. The rules in order to inject these divs are pretty tricky:
They need to be injected always after a paragraph.
There needs to be a minimum amount of words before the first injected div.
There needs to be a minimum amount of words before the previous injected div and the one that is being inserted right now.
There needs to be a minimum amount of words between the currently injected div and some specific elements in the content.
I have created a function that aspires to do the things mentioned above and looks like this:
function elementProximityCheck(paragraphOffset, wordSpace, elSelector) {
var elementsToCheck = ['figure', 'blockquote'];
var wordCounter, paragraphOffsetMin, paragraphOffsetMax;
wordCounter = 0;
paragraphOffsetMin = paragraphOffset-1;
do {
wordCounter += numOfWords(paragraphOffsetMin, elSelector);
paragraphOffsetMin -= 1;
} while (wordCounter < wordSpace);
wordCounter = 0;
paragraphOffsetMax = paragraphOffset;
do {
wordCounter += numOfWords(paragraphOffsetMax, elSelector);
paragraphOffsetMax += 1;
} while (wordCounter < wordSpace);
$(elSelector).slice(paragraphOffsetMin, paragraphOffsetMax + 1).each(function () {
for (var elementIndex = 0; elementIndex < elementsToCheck.length; elementIndex++) {
if ($(this).is(elementsToCheck[elementIndex])) {
paragraphOffset = elementProximityCheck(paragraphOffsetMax, wordSpace, elSelector);
return false;
}
}
});
return paragraphOffset;
}
This function is called after I have identified the first paragraph that meets the 2nd condition above.
The main idea is that I search backward and forward for an element of the elementsToCheck array and if such an element is found, the function calls itself recursively with a new paragraph index (which is the paragraph that I used as a bound in the previous execution).
The problem is that this thing does not work and I can't really figure out why. Any insights (or alternative solutions) will be highly appreciated.
Edit:
Below you can find a JSFiddle implementation of my code along with the extra function that I'm using to count the words.
JSFiddle Example
Edit 2: Using the JSFiddle, the paragraphIndex that I'm getting back is always the same paragraph that I provide. In theory, the code should move/increase the paragraph index when a figure or blockquote is found in the "wordSpace proximity" of the current paragraph element.
For example, if the wordSpace variable is 60, then assuming that we start looking from the 3rd paragraph (paragraphIndex = 2), in the first execution of the function, the paragraphOffsetMin should be 0, paragraphOffsetMax should be 3, and then it should call itself with 3 as the paragraphIndex. In the recursive call, the paragraphOffsetMin should be 2 and paragraphOffsetMax should be 4 and this is what should be returned in the end.
What I'm basically trying to do is inject the elements using specific "word distances" between them and also "avoiding the hurdles of blockquotes/figures. I suspect that there are better algorithms than the one that I provided though.

Categories