Capturing "Selstart", "Sel..end" within a non-input element - javascript

I'm setting up a table, formatted like this.. Please forgive the silly sample data. All of this is actually loaded with json, but the output looks like this:
<table id="famequotes">
<tr><td id="par001">It was the best of times, it was the worst of times.</td></tr>
<tr><td id="par002">Two things are infinite: the universe and human stupidity; and I'm not sure about the universe</td></tr>
<tr><td id="par003">In the jungle, the mighty jungle, the lion sleeps tonight...</td></tr>
</table>
Using mousedown on $("#famequotes td"), and mouseup, I can allow users to highlight text and capture the start and end row, and similarly capture all the rows in between with onmouseover.
What I want to do is capture the SelStart of the start row (the table cell I started highlighting from) and the sel...stop of the last row.
I have not been able to find an answer, though I have researched and google searched already.
MIchal Hainc's answer is really cool, but not what I am seeking.
I can see how the selection box could be useful, and I would love to incorporate it.
However, what I'm actually seeking is so that if the user highlights text as I've highlighted in http://jsfiddle.net/vhyyzeog/1/ it would return 3 for selStart and 12 for selEnd. I'm already tracking the first highlighted row, and the last, so I know what rows to apply selstart and selend to.
Thank you for any help.

I have constructed a fiddle for you here: http://jsfiddle.net/jra9ttot/16/
I have used jQuery UI widget "selectable"... the javascript is like this:
You can select like in windows explorer... drag a lasso with the mouse over your table cells, it will
select multiple...
$(document).ready(function () {
$('#famequotes').selectable({
filter: 'td',
start: function (event, ui) {
$('#log').append('<div>selction start: ' + $(event.toElement).attr('id') + '</div>');
},
selected: function (event, ui) {
$('#log').append('<div>selected item: ' + $(ui.selected).attr('id') + '</div>');
},
stop: function (event, ui) {
var lastSelElem = $('#famequotes').find('td.ui-selected').last();
$('#log').append('<div>selection end: ' + $(lastSelElem).attr('id') + '</div>');
}
});
});

I found an answer to this question elsewhere on Stack's site. It's not as pretty and perfect as I'd imagined it might be.
Source: Hussein's answer to "Get selected text's html in div"
Here's sort of the groundwork for how you can obtain useful data from this, if you want more than the basic selected text.
http://jsfiddle.net/YstZn/541/
if (!window.x) {
x = {};
}
x.Selector = {};
x.Selector.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
console.log(window.getSelection().anchorNode.parentNode);
console.log(window.getSelection().focusNode.parentNode);
return t;
}
$(document).ready(function() {
$(document).bind("mouseup", function() {
var mytext = x.Selector.getSelected();
alert(mytext);
});
});
It's not quite what I'd imagined, but it definitely provides the elements I need to accomplish my task.

Related

JQuery: Finding a way to name cloned input fields

I'm not the best at using jQuery, but I do require it to be able to make my website user-friendly.
I have several tables involved in my website, and for each the user should be able to add/delete rows. I created a jquery function, with help from stackoverflow, and it successfully added/deleted rows. Now the only problem with this is the names for those input fields is slightly messed up. I would like each input field to be an array: so like name[0] for the first row, name[1] for the second row, etc. I have a bunch of tables all with different inputs, so how would I make jQuery adjust the names accordingly?
My function, doesn't work completely, but I do not know how to go about changing it.
My Jquery function looks like:
$(document).ready(function() {
$("body").on('click', '.add_row', function() {
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
var clone = tr.clone();
clone.find("input").val('');
clone.find("select").val('');
clone.find('input').each(function(i) {
$(this).attr('name', $(this).attr('name') + i);
});
clone.find('select').each(function(i) {
$(this).attr('name', $(this).attr('name') + i);
});
tr.after(clone);
});
$("body").on('click', '.delete_row', function() {
var rowCount = $(this).closest('.row').prev('table').find('tr.ia_table').length;
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
if (rowCount > 1) {
tr.remove();
};
});
});
I also created a jsFiddle here: https://jsfiddle.net/tareenmj/err73gLL/.
Any help is greatly appreciated.
UPDATE - Partial Working Solution
After help from a lot of users, I was able to create a function which does this:
$("body").on('click', '.add_row', function() {
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
var clone = tr.clone();
clone.find("input").val('');
clone.find("select").val('');
clone.find('input').each(function() {
var msg=$(this).attr('name');
var x=parseInt(msg.split('[').pop().split(']').shift());
var test=msg.substr(0,msg.indexOf('['))+"[";
x++;
x=x.toString();
test=test+x+"]";
$(this).attr('name', test);
});
clone.find('select').each(function() {
var msg1=$(this).attr('name');
var x1=parseInt(msg1.split('[').pop().split(']').shift());
var test1=msg1.substr(0,msg1.indexOf('['))+"[";
x1++;
x1=x1.toString();
test1=test1+x1+"]";
$(this).attr('name', test1);
});
tr.after(clone);
});
A working jsFiddle is here: https://jsfiddle.net/tareenmj/amojyjjn/2/
The only problem is that if I do not select any of the options in the select inputs, it doesn't provide me with a value of null, whereas it should. Any tips on fixing this issue?
I think I understand your problem. See if this fiddle works for you...
This is what I did, inside each of the clone.find() functions, I added the following logic...
clone.find('input').each(function(i) {
// extract the number part of the name
number = parseInt($(this).attr('name').substr($(this).attr('name').indexOf("_") + 1));
// increment the number
number += 1;
// extract the name itself (without the row index)
name = $(this).attr('name').substr(0, $(this).attr('name').indexOf('_'));
// add the row index to the string
$(this).attr('name', name + "_" + number);
});
In essence, I separate the name into 2 parts based on the _, the string and the row index. I increment the row index every time the add_row is called.
So each row will have something like the following structure when a row is added...
// row 1
sectionTB1_1
presentationTB1_1
percentageTB1_1
courseTB1_1
sessionTB1_1
reqElecTB1_1
// row 2
sectionTB1_2
presentationTB1_2
percentageTB1_2
courseTB1_2
sessionTB1_2
reqElecTB1_2
// etc.
Let me know if this is what you were looking for.
Full Working Solution for Anyone Who needs it
So after doing loads and loads of research, I found a very simple way on how to do this. Instead of manually adjusting the name of the array, I realised that the clone method will do it automatically for you if you supply an array as the name. So something like name="name[]" will end up working. The brackets without any text has to be there. Explanation can't possible describe the code fully, so here is the JQuery code required for this behaviour to work:
$(document).ready(function() {
$("body").on('click', '.add_row', function() {
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
var clone = tr.clone();
clone.find("input").val('');
tr.after(clone);
});
$("body").on('click', '.delete_row', function() {
var rowCount =
$(this).closest('.row').prev('table').find('tr.ia_table').length;
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
if (rowCount > 1) {
tr.remove();
};
});
});
A fully working JSfiddle is provided here: https://jsfiddle.net/tareenmj/amojyjjn/5/
Just a tip, that you have to be remove the disabled select since this will not pass a value of null.

Kendo grid cell refocusing after refresh

I have a selectable, navigatable and editable grid. After I enter a value in a cell, I have to change the value in the cell under the updated cell. To show the updated values of both cells, I have to refresh the grid. When I do that, the edited cell loses focus. I found a way to refocus the last edited cell during the save event:
save: function (e) {
var focusedCellIndex = this.current()[0].cellIndex; //gets the cell index of the currently focused cell
//...some dataItem saving (dataItem.set()) logic...
this.refresh(); //refreshing the grid instance
setTimeout(function () { //refocusing the cell
return function () {
var focusedCell = $("#grid tr[data-uid='" + dataItem.uid + "'] td:nth-child(" + (focusedCellIndex + 1) + ")");
$('#grid').data('kendoGrid').editCell(focusedCell);
}
}(), 200);
}
The problem is that this works for the first time, but if I try to re-edit the same cell again, the cell loses focus. When I try to debug, it seems that this.current()[0].cellIndex returns 0 in the second attempt, and because of that cell focusing isn't working anymore.
Does anyone have any idea why this.current() works for the 1st time, and not for the 2nd time? Are there any other approaches for refocusing the cell?
It's difficult to say exactly what is happening without seeing it in a demo, so if you can, I'd suggest creating one for illustration. I'm guessing that the call to refresh is removing the current cell selection and focusing the first cell because the grid is navigatable (I don't quite understand the rationale behind that behavior, but it's hard to say whether it's a bug since we don't get to read Telerik's code comments).
One approach that might work would be to modify the current method to also store the current cell index:
kendo.ui.Grid.fn.refresh = (function(refresh) {
return function(e) {
this._refreshing = true;
refresh.call(this, e);
this._refreshing = false;
}
})(kendo.ui.Grid.fn.refresh);
kendo.ui.Grid.fn.current = (function(current) {
return function(element) {
// assuming element is td element, i.e. cell selection
if (!this._refreshing && element) {
this._lastFocusedCellIndex = $(element).index(); // note this might break with grouping cells etc, see grid.cellIndex() method
this._lastFocusedUid = $(element).closest("tr").data("uid");
}
return current.call(this, element);
}
})(kendo.ui.Grid.fn.current);
kendo.ui.Grid.fn.refocusLastEditedCell = function () {
if (this._lastFocusedUid ) {
var row = $(this.tbody).find("tr[data-uid='" + this._lastFocusedUid + "']");
var cell = $(row).children().eq(this._lastFocusedCellIndex);
this.editCell(cell);
}
};
That way, you should always be able to use grid.refocusLastEditedCell() when you need to.
Another idea:
save: function (e) {
var focusedCell = this.current();
var focusedCellIndex = focusedCell.index(); //gets the cell index of the currently focused cell
//...some dataItem saving (dataItem.set()) logic...
this.refresh(); //refreshing the grid instance
// reset current cell..
this.current(focusedCell);
setTimeout(function () { //refocusing the cell
return function () {
var focusedCell = $("#grid tr[data-uid='" + dataItem.uid + "'] td:nth-child(" + (focusedCellIndex + 1) + ")");
$('#grid').data('kendoGrid').editCell(focusedCell);
}
}(), 200);
}
I don't have enough reputation to comment on the answer, but I'd like to thank you Lars Hoppner for your answer. It helped me tremendously with the annoying refresh navigation problems of Kendo Grids.
I also would like to add that for grids with horizontal scroll bars, your solution will still cause the scroll to shift as far left as possible while keeping the last edited cell in view. To prevent this bad behavior, I did the following:
grid.closeCell();
grid.refresh();
grid.refocusLastEditedCell();
Closing the cell before refreshing kept the scroll bars in place, now everything works great. Hopefully this helps anyone else viewing your answer.

Duck Punching / Monkey Patching breaks Tablesorter

I have a textbox that comma separated/delimited values are entered into which I have to make sure has unique entries. Solved that using Paul Irish's Duck Punching example #2 and tying it to onblur for that textbox.
The values entered into the textbox get broken out into a table. As the table can get very lengthy, I found Mottie's Tablesorter to work brilliantly.
The problem is, the the Duck Punching code is breaking the Tablesorter. The style for the Tablesorter is passed through just fine, but the table doesn't actually sort. BUT, when I comment out the Duck Punching code, Tablesorter miraculosly works.
My coding skills are not such that I can figure out why the two are conflicting. Any assistance would be much appreciated.
I haven't modified the Tablesorter code or added any special sorting elements to it...just following the very basic example right now. Here's the Duck Punching code which I've only modified to include the var for the textbox I need to have unique entries.
function ValidateTextBox1()
{
(function($){
var arr = document.getElementById("TextBox1").value.split(',');
var _old = $.unique;
$.unique = function(arr){
// do the default behavior only if we got an array of elements
if (!!arr[0].nodeType){
return _old.apply(this,arguments);
} else {
// reduce the array to contain no dupes via grep/inArray
return $.grep(arr,function(v,k){
return $.inArray(v,arr) === k;
});
}
};
})(jQuery);
}
The function above is in a separate js file which is called via onblur for TextBox1.
Then, I have a button which runs the following:
function GenerateTable()
{
var Entry1 = document.getElementById("TextBox1").value
var Entry2 = document.getElementById("TextBox2").value
var content = "<table id=myTable class=tablesorter ><thead><tr><th>Entry 1 Values</th><th>Entry 2 Value</th></tr></thead><tbody>"
var lines = Entry1.split(","),
i;
for (i = 0; i < lines.length; i++)
content += "<tr><td>" + (Entry1.split(",")[i]) + "</td><td>" + Entry2 + "</td></tr>";
content += "</tbody></table>"
$("#here_table").append(content);
$(function(){
$("#myTable").tablesorter({
theme: 'default'
});
});
}
The function will generate/append the table in a specific DIV.
If I leave in the validation code for TextBox1, the table will generate but isn't sortable (though it does manage to still pull the theme).
If I remove the validation code, the table will generate and is sortable.
The validateText box function above will not work as expected. In this case, "duck-punching" is not even necessary.
Here is how I would fix the script (demo):
HTML
<textarea id="textbox1">6,1,7,5,3,4,3,2,4</textarea><br>
<textarea id="textbox2">column 2</textarea><br>
<button>Build Table</button>
<div id="here_table"></div>
Script (requires jQuery 1.7+)
(function($) {
// bind to button
$(function () {
$('button').on('click', function () {
// disable button to prevent multiple updates
$(this).prop('disabled', true);
generateTable();
});
});
function unique(arr) {
return $.grep(arr, function (v, k) {
return $.inArray(v, arr) === k;
});
}
function generateTable() {
var i,
$wrap = $('#here_table'),
// get text box value, remove unwanted
// spaces/tabs/carriage returns & create an array
val = $('#textbox1').val().split(/\s*,\s*/),
// get unique values for Entry1
entry1 = unique( val ),
entry2 = $('#textbox2').val(),
content = "";
// build tbody rows
for (i = 0; i < entry1.length; i++) {
content += "<tr><td>" + (entry1[i] || '?') + "</td><td>" + entry2 + "</td></tr>";
}
// update or create table
if ($wrap.find('table').length) {
// table exists, just update the data
$wrap.find('tbody').remove();
$wrap.find('table')
.append(content)
.trigger('update');
} else {
// table doesn't exist, build it from scratch
$wrap
.html('<table id=myTable class=tablesorter><thead><tr>' +
'<th>Entry 1 Values</th>' +
'<th>Entry 2 Value</th>' +
'</tr></thead><tbody>' + content + '</tbody></table>')
.find('table')
.tablesorter({
theme: 'blue'
});
}
// enable the button to allow updating the table
$('button').prop('disabled', false);
}
})(jQuery);
I tried to add a few comments to make more clear what each step is doing. Please feel free to ask for any clarification.

Select previous and next words when double clicking blank space in HTML with Javascript

I was trying to change the behaviour of an HTML page with Javascript so whenever I click a blank space in a text (not textArea) between two words, instead of selecting that blank space, it selects the words before and after the blank space. I was trying to do it this way, but I am not able to do it:
function getBothWords() {
if (window.getSelection()) {
var sel = window.getSelection();
var blank = " ";
if(sel == blank) {
...
}
}
}
I also was trying to play with:
window.getSelection().getRangeAt(0)
But still nothing. Any ideas? Thanks :)
When you only click on a blank space, the selection will be collapsed. You can try something like this:
function getBothWords() {
var sel,
range = document.getSelection().getRangeAt(0);
if (range.collapsed) {
range.setEnd(range.startContainer, range.startOffset + 1);
}
sel = range.toString();
if (sel === ' ') {
...
}
}
A live demo at jsFiddle.
You should introspect the selection object and see that there is an anchorNode element available and an anchorIndex.
See the MDN Docs on selection.
The short is you need to look at your anchorNode and anchorOffset.
sel.anchorNode.nodeValue[sel.anchorNode.anchorOffset] might be the first character of your selection. Log the sel object to the console and start poking around. Some simple math should solve the issue from there. Of course it might not be a text node.
Be sure to read the definitions on the page since there are some gotchas that can be confusing.
More to the point, something like this is a dirty hack job:
var subLen = s.focusNode.nodeValue.substr(s.focusOffset).trim().indexOf(' ')+2;
var selectedWordWitSurroundingSpaces = s.focusNode.nodeValue.substr(s.focusOffset, subLen);
There is an amazing method in class Selection called modify() which is created for that purpose. In my case, the solution would be:
function select() {
if (window.getSelection) {
var s = window.getSelection();
var blank = " ";
if(s == blank) {
selectBothWords(s);
}
}
}
function selectBothWords(s) {
s.modify("move", "backward", "word");
s.modify("extend", "forward", "word");
s.modify("extend", "forward", "word");
}
The function select() checks that the selection is a 'blank space' (loosely). Then, the function selectBothWords() uses modify to move the selection one word backwards, then, extend it two words forward.

Kendo Grid how to programmatically focus a grid cell and block select the text

I have a kendo grid with edit mode set to incell.
What is the most elegant way of programmatically focus a particular cell to force it into an edit mode?
Let's say I have a grid where column 1 and 6 are editable. As soon as user finishes typing something into column 1, I'd like the column 6 textbox to be automatically focused and enabled for editing so that the user doesn't have to manually click on the next editable gridcell for the same row.
This is what I'm doing at the moment:
//Focuses the editable cell at given row/column index.
//Closes the previously editing cell
//EX: setGridFocus(SALE_01_DIV_GRID,0,0) --> Focuses SALE_01_DIV_GRID (0,0)
function setGridFocus(gridID, rowIndex, colIndex)
{
var grid = $('#' + gridID).data('kendoGrid');
grid.closeCell();
setTimeout(function(){
var cell = $('#' + gridID).find('tbody tr:eq('+rowIndex+') td:eq('+colIndex+')');
grid.editCell(cell);
var editTextCell = cell.find("input.k-formatted-value");
editTextCell.focus(function() {
$(this).select().mouseup(function (e) {
e.preventDefault();
$(this).unbind("mouseup");
e.select();
});
});
cell.find("input[type=text]").select();
editTextCell.selectall();
},50);
}
First of all, I'm using setTimeout to implement what is seemingly a trivial function so this doesn't seem like the ideal approach.
Secondly, the above function only works when it feels like it (Only works half of the time from testing. Probably expected from setTimeout function). I feel like this has to do with the order of events called internally in Kendo Grid.
Thirdly, I'd like to block select the text that's inside the cell being focused. editTextCell.selectall(); doesn't work for this purpose.
I'd appreciate any guidance.
This is reliable way to achieve what you want. It still uses setTimeout, so it can reliably focus different kendo inputs (Docs source for focusing kendo inputs):
function setGridFocus(gridID, rowIndex, colIndex) {
var grid = $('#' + gridID).data('kendoGrid');
grid.closeCell();
var cell = $('#' + gridID).find('tbody tr:eq(' + rowIndex + ') td:eq(' + colIndex + ')');
grid.editCell(cell);
var editTextCell = cell.find('input');
var selectTimeId;
editTextCell
.on('focus', function() {
var input = $(this);
clearTimeout(selectTimeId); // stop started time out if any
selectTimeId = setTimeout(function() {
input.select();
// To make this work on iOS, too, replace the above line with the following one. Discussed in https://stackoverflow.com/q/3272089
// input[0].setSelectionRange(0, 9999);
});
})
.blur(function(e) {
clearTimeout(selectTimeId); // stop started timeout
});
editTextCell.focus();
}

Categories