Traversing contenteditable paragraphs with arrow keys - javascript

I'm try to traverse between contenteditable paragraphs using the arrow keys. I can't put a containing div around all paragraphs as the may be divided by other non-editable elements.
I need to be able to determine the character length of the first line so that when the up arrow key is pressed when the cursor is on the line then it will jump up to the previous paragraph - hopefully keeping the cursor position relative to the line.
I can get the cursor index with:
function cursorIndex() {
return window.getSelection().getRangeAt(0).startOffset;
}
and set it with: as found here - Javascript Contenteditable - set Cursor / Caret to index
var setSelectionRange = function(element, start, end) {
var rng = document.createRange(),
sel = getSelection(),
n, o = 0,
tw = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, null);
while (n = tw.nextNode()) {
o += n.nodeValue.length;
if (o > start) {
rng.setStart(n, n.nodeValue.length + start - o);
start = Infinity;
}
if (o >= end) {
rng.setEnd(n, n.nodeValue.length + end - o);
break;
}
}
sel.removeAllRanges();
sel.addRange(rng);
};
var setCaret = function(element, index) {
setSelectionRange(element, index, index);
};
Say the cursor is at the top row of the third paragraph and the up arrow is pressed, I would like it to jump to the bottom row of the second paragraph
http://jsfiddle.net/Pd52U/2/

Looks like there's no easy way to do this, I have the following working example. There's a bit of processing so it's a little slow and it can be out by the odd character when moving up and down between paragraph.
Please inform me of any improvements that can be made.
http://jsfiddle.net/zQUhV/47/
What I've done is split the paragraph by each work, insert them into a new element one by one, checking for a height change - when it does change a new line was added.
This function returns an array of line objects containing the line text, starting index and end index:
(function($) {
$.fn.lines = function(){
words = this.text().split(" "); //split text into each word
lines = [];
hiddenElement = this.clone(); //copies font settings and width
hiddenElement.empty();//clear text
hiddenElement.css("visibility", "hidden");
jQuery('body').append(hiddenElement); // height doesn't exist until inserted into document
hiddenElement.text('i'); //add character to get height
height = hiddenElement.height();
hiddenElement.empty();
startIndex = -1; // quick fix for now - offset by one to get the line indexes working
jQuery.each(words, function() {
lineText = hiddenElement.text(); // get text before new word appended
hiddenElement.text(lineText + " " + this);
if(hiddenElement.height() > height) { // if new line
lines.push({text: lineText, startIndex: startIndex, endIndex: (lineText.length + startIndex)}); // push lineText not hiddenElement.text() other wise each line will have 1 word too many
startIndex = startIndex + lineText.length +1;
hiddenElement.text(this); //first word of the next line
}
});
lines.push({text: hiddenElement.text(), startIndex: startIndex, endIndex: (hiddenElement.text().length + startIndex)}); // push last line
hiddenElement.remove();
lines[0].startIndex = 0; //quick fix for now - adjust first line index
return lines;
}
})(jQuery);
Now you could use that to measure the number of character up until the point of the cursor and apply that when traversing paragraph to keep the cursor position relative to the start of the line. However that can produce wildly inaccurate results when considering the width of an 'i' to the width of an 'm'.
Instead it would be better to find the width of the line up to the point of the cursor:
function distanceToCaret(textElement,caretIndex){
line = findLineViaCaret(textElement,caretIndex);
if(line.startIndex == 0) {
// +1 needed for substring to be correct but only first line?
relativeIndex = caretIndex - line.startIndex +1;
} else {
relativeIndex = caretIndex - line.startIndex;
}
textToCaret = line.text.substring(0, relativeIndex);
hiddenElement = textElement.clone(); //copies font settings and width
hiddenElement.empty();//clear text
hiddenElement.css("visibility", "hidden");
hiddenElement.css("width", "auto"); //so width can be measured
hiddenElement.css("display", "inline-block"); //so width can be measured
jQuery('body').append(hiddenElement); // doesn't exist until inserted into document
hiddenElement.text(textToCaret); //add to get width
width = hiddenElement.width();
hiddenElement.remove();
return width;
}
function findLineViaCaret(textElement,caretIndex){
jQuery.each(textElement.lines(), function() {
if(this.startIndex <= caretIndex && this.endIndex >= caretIndex) {
r = this;
return false; // exits loop
}
});
return r;
}
Then split the target line up into characters and find the point that closest matches the width above by adding characters one by one until the point is reached:
function getCaretViaWidth(textElement, lineNo, width) {
line = textElement.lines()[lineNo-1];
lineCharacters = line.text.replace(/^\s+|\s+$/g, '').split("");
hiddenElement = textElement.clone(); //copies font settings and width
hiddenElement.empty();//clear text
hiddenElement.css("visibility", "hidden");
hiddenElement.css("width", "auto"); //so width can be measured
hiddenElement.css("display", "inline-block"); //so width can be measured
jQuery('body').append(hiddenElement); // doesn't exist until inserted into document
if(width == 0) { //if width is 0 index is at start
caretIndex = line.startIndex;
} else {// else loop through each character until width is reached
hiddenElement.empty();
jQuery.each(lineCharacters, function() {
text = hiddenElement.text();
prevWidth = hiddenElement.width();
hiddenElement.text(text + this);
elWidth = hiddenElement.width();
caretIndex = hiddenElement.text().length + line.startIndex;
if(hiddenElement.width() > width) {
// check whether character after width or before width is closest
if(Math.abs(width - prevWidth) < Math.abs(width - elWidth)) {
caretIndex = caretIndex -1; // move index back one if previous is closes
}
return false;
}
});
}
hiddenElement.remove();
return caretIndex;
}
That with the following keydown function is enough to traverse pretty accurately between contenteditable paragraphs:
$(document).on('keydown', 'p[contenteditable="true"]', function(e) {
//if cursor on first line & up arrow key
if(e.which == 38 && (cursorIndex() < $(this).lines()[0].text.length)) {
e.preventDefault();
if ($(this).prev().is('p')) {
prev = $(this).prev('p');
getDistanceToCaret = distanceToCaret($(this), cursorIndex());
lineNumber = prev.lines().length;
caretPosition = getCaretViaWidth(prev, lineNumber, getDistanceToCaret);
prev.focus();
setCaret(prev.get(0), caretPosition);
}
// if cursor on last line & down arrow
} else if(e.which == 40 && cursorIndex() >= $(this).lastLine().startIndex && cursorIndex() <= ($(this).lastLine().startIndex + $(this).lastLine().text.length)) {
e.preventDefault();
if ($(this).next().is('p')) {
next = $(this).next('p');
getDistanceToCaret = distanceToCaret($(this), cursorIndex());
caretPosition = getCaretViaWidth(next, 1, getDistanceToCaret);
next.focus();
setCaret(next.get(0), caretPosition);
}
//if start of paragraph and left arrow
} else if(e.which == 37 && cursorIndex() == 0) {
e.preventDefault();
if ($(this).prev().is('p')) {
prev = $(this).prev('p');
prev.focus();
setCaret(prev.get(0), prev.text().length);
}
// if end of paragraph and right arrow
} else if(e.which == 39 && cursorIndex() == $(this).text().length) {
e.preventDefault();
if ($(this).next().is('p')) {
$(this).next('p').focus();
}
};

Related

Create a new div and display it where the mouse cursor is on page

At the moment, the circles are being appended to <p> in the html markup.
You can see how I have done this below.
But what I actually want is for the div to be created where the mouse cursor is the on the page.
How might I get the position of the cursor and create a div there?
Thanks.
var count = 0;
/*
* Function(makeLetters)
* Removes first character in text box.
* Creates circle Letters using first character.
*/
$(document).click(function (makeLetters) {
//Get characters currently entered into textbox
var letters = $('#letters').val();
//Take the firstLetter only
var firstLetter = letters.substr(0, 1);
//Count how many characters currently entered
var numberOfLetters = letters.length;
//Use length of entry to take all characters except first
var restOfLetters = letters.substr(1, numberOfLetters);
//All characters except first set as textbox entry
$("#letters").val(restOfLetters);
//Check that there is a first character
if (firstLetter != "") {
count++;
//Create a new circle letter by inserting new div class
$("p").append('<div class="circle' + count + '" style="border-radius: 50%;width: 36px;height:36px;padding:8px;background:#FF7D5C;color:black;text-align:center;font:32px Arial, sans-serif;display:inline-block;margin-right:4px;margin-bottom:4px;">' + firstLetter.toUpperCase() + '</div>');
$("p").children().last().hide().fadeIn();
}
});
As far as I'm aware you cant get the mouse position on its own, but you could use jQuery to listen to the mousemove event and store the position in a variable for use later like so:
var mousePosition = { x: 0, y: 0 };
$(document).mousemove(function(e) {
mousePosition.x = e.pageX;
mousePosition.y = e.pageY;
});
Then you would need to use postion: [absolute/fixed]; to position the div based on the mousePosition.
You'd do that by getting the mouse position from the event, that you for some strange reason named makeLetters ?
$(document).click(function(event) {
var letters = $('#letters').val();
var firstLetter = letters.substr(0, 1);
var numberOfLetters = letters.length;
var restOfLetters = letters.substr(1, numberOfLetters);
var mouseLeft = event.pageX;
var mouseTop = event.pageY;
$("#letters").val(restOfLetters);
if (firstLetter != "") {
count++;
var div = $('<div />', {
'class' : 'circle' + count,
text : firstLetter.toUpperCase(),
css : {
position: 'fixed',
top : mouseTop + 'px',
left : mouseLeft + 'px',
display : 'none'
// ... the rest of the styles here
}
});
$("p").append( div.fadeIn() );
}
});

JS function that scrolls an element into view taking into account possible scrollable and positioned parent

I was looking for a function that would scroll a given element into view with some smart behavior:
if an element is descendant of a scrollable element - that ancestor is scrolled rather than body.
if an element is descendant of a positioned element - body won't be scrolled.
I didn't find any suitable function, so I made one and wanted some expert opinion on it. Please check the plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview . There are problems with animated scroll in FF, so please use Chrome to check the logic.
To illustrate, what I'm looking for - here is the first update that came to mind - if we reached an element that can scroll, lets call it SC (Scroll Parent), we should not only scroll SC to make the target visible inside it, but also recursively scroll SC itself into view, since it may outside of the currently visible are of the page. Here is the update plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview (also applied fix for FF scrolling problem).
And here is the code of the function
function scrollTo(target){
//Position delta is used for scrollable elements other than BODY
var combinedPositionDelta = 0;
var previousParent = $(target);
var parent = $(target).parent();
while(parent){
combinedPositionDelta += previousParent.position().top - parent.position().top;
//If we reached body
if(parent.prop("tagName").toUpperCase() == "BODY"){
scrollBody(target.offset().top);
break;
}
//if we reached an element that can scroll
if(parent[0].scrollHeight > parent.outerHeight()){
scrollElementByDelta(parent,combinedPositionDelta);
//Recursively scroll parent into view, since it itself might not be visible
scrollTo(parent);
break;
}
//if we reached a apositioned element - break
if(parent.css('position').toUpperCase() != 'STATIC'){
console.log("Stopping due to positioned parent " + parent[0].outerHTML);
break;
}
previousParent = parent;
parent = parent.parent();
}
}
var offsetSkin = 20;
function scrollElementByDelta(element,offsetDelta){
$(element).animate({
scrollTop: element.scrollTop() + (offsetDelta - offsetSkin)
}, 1000);
}
function scrollBody(offset){
$('body,html').animate({
scrollTop: offset - offsetSkin
}, 1000);
}
Well I'm Using this one which works very well for me:
function scrollIntoView (element, alignTop) {
var document = element.ownerDocument;
var origin = element, originRect = origin.getBoundingClientRect();
var hasScroll = false;
var documentScroll = this.getDocumentScrollElement(document);
while (element) {
if (element == document.body) {
element = documentScroll;
} else {
element = element.parentNode;
}
if (element) {
var hasScrollbar = (!element.clientHeight) ? false : element.scrollHeight > element.clientHeight;
if (!hasScrollbar) {
if (element == documentScroll) {
element = null;
}
continue;
}
var rects;
if (element == documentScroll) {
rects = {
left : 0,
top : 0
};
} else {
rects = element.getBoundingClientRect();
}
// check that elementRect is in rects
var deltaLeft = originRect.left - (rects.left + (parseInt(element.style.borderLeftWidth, 10) | 0));
var deltaRight = originRect.right
- (rects.left + element.clientWidth + (parseInt(element.style.borderLeftWidth, 10) | 0));
var deltaTop = originRect.top - (rects.top + (parseInt(element.style.borderTopWidth, 10) | 0));
var deltaBottom = originRect.bottom
- (rects.top + element.clientHeight + (parseInt(element.style.borderTopWidth, 10) | 0));
// adjust display depending on deltas
if (deltaLeft < 0) {
element.scrollLeft += deltaLeft;
} else if (deltaRight > 0) {
element.scrollLeft += deltaRight;
}
if (alignTop === true && !hasScroll) {
element.scrollTop += deltaTop;
} else if (alignTop === false && !hasScroll) {
element.scrollTop += deltaBottom;
} else {
if (deltaTop < 0) {
element.scrollTop += deltaTop;
} else if (deltaBottom > 0) {
element.scrollTop += deltaBottom;
}
}
if (element == documentScroll) {
element = null;
} else {
// readjust element position after scrolls, and check if vertical scroll has changed.
// this is required to perform only one alignment
var nextRect = origin.getBoundingClientRect();
if (nextRect.top != originRect.top) {
hasScroll = true;
}
originRect = nextRect;
}
}
}
}
I hope this helps.
If you do not mind venturing into jQuery, the scrollTo plugin is the best bet. It handles most needs and gives a very refined smooth trasition.
Hope it helps.

Max lines textarea

I have found some scripts that limit the lines used in a textarea like this:
$(document).ready(function(){
var lines = 10;
var linesUsed = $('#linesUsed');
var newLines=0;
$('#rev').keydown(function(e) {
newLines = $(this).val().split("\n").length;
linesUsed.text(newLines);
if(e.keyCode == 13 && newLines >= lines) {
linesUsed.css('color', 'red');
return false;
}
else {
linesUsed.css('color', '');
}
});
It works fine when you hit enter and limits it to 10 .But the problem occurs when you type sentences that are so long they automatically go to a new line without the \n and when you copy paste a text, then it fails to limit the lines used.
does anyone know how to fix this.
Important: solution needs to work for a textarea
You could try doing it using this logic:
JS :
var limit = 3; // <---max no of lines you want in textarea
var textarea = document.getElementById("splitLines");
var spaces = textarea.getAttribute("cols");
textarea.onkeyup = function() {
var lines = textarea.value.split("\n");
for (var i = 0; i < lines.length; i++)
{
if (lines[i].length <= spaces) continue;
var j = 0;
var space = spaces;
while (j++ <= spaces)
{
if (lines[i].charAt(j) === " ") space = j;
}
lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || "");
lines[i] = lines[i].substring(0, space);
}
if(lines.length>limit)
{
textarea.style.color = 'red';
setTimeout(function(){
textarea.style.color = '';
},500);
}
textarea.value = lines.slice(0, limit).join("\n");
};
Here is the UPDATED DEMO
Well, I couldn't figure out how to calculate the height of only the text inside a textarea, so I used a contenteditable div instead. Hope you like this solution.
HTML
<div id="container">
<div id="rev" contenteditable="true"></div>
</div>
CSS
#container {
height:100px;
width:300px;
cursor:text;
border:1px solid #000
}
#rev {
line-height:20px;
outline:none
}
JS
$(document).ready(function () {
$('#container').click(function() {
$('#rev').focus();
});
var limit = 3;
var lineHeight = parseInt($('#rev').css('line-height'));
$('#rev').keydown(function (e) {
var totalHeight = parseInt($('#rev').height());
var linesUsed = totalHeight / lineHeight;
if (e.keyCode == 13 && linesUsed >= limit) {
$('#rev').css('color', 'red');
return false;
} else {
$('#rev').css('color', '');
}
});
});
HERE IS A DEMO YOU CAN FIDDLE WITH
MAJOR EDIT
Following the OP pointing out I actually forgot to address the most important, I updated my code. I basically removed the check for the enter key and allowed the delete and backspace keys in case the text goes over the limit as follows. You may have to fiddle around with it a little to make it fit to your exact needs.
$(document).ready(function () {
$('#container').click(function() {
$('#rev').focus();
});
var limit = 3;
var lineHeight = parseInt($('#rev').css('line-height'));
$('#rev').keydown(function (e) {
var totalHeight = parseInt($('#rev').height());
var linesUsed = totalHeight / lineHeight;
if (linesUsed > limit) { // I removed 'e.keyCode == 13 &&' from here
$('#rev').css('color', 'red');
if (e.keyCode != 8 && e.keyCode != 46) return false; // I added this check
} else {
$('#rev').css('color', '');
}
});
// I added the following lines
$('#rev').on('paste', function () {
if (linesUsed > limit) {
$('#rev').css('color', 'red');
if (e.keyCode != 8 && e.keyCode != 46) return false;
} else {
$('#rev').css('color', '');
}
});
});
UPDATED DEMO HERE
It's too much work to try to figure how many lines based on the number characters in each line and the textarea width (does the textarea have wrapping off or not? font size, different letter widths, spaces, etc...). The easiest way is to have two textareas (one visible and one not - height set to 0) with the same width and font styles and check the scroll height of the invisible textarea.
Here is an example http://jsfiddle.net/SKYt4/1/
HTML
<textarea id="visible_textarea"></textarea>
<textarea id="hidden_textarea"></textarea> <!-- hidden by setting the height to 0 -->
<div id="used_lines"></div>
CSS
textarea {line-height:16px; /* line height to calculate number of lines */
width:150px; /* need to match width */}
#hidden_textarea {height:0px;
padding:0px;
border:none;
margin:0px;
opacity:0;}
JavaScript
$('#visible_textarea').keyup(function(){
$('#hidden_textarea').val(this.value);
// checking how many lines
var lns = Math.ceil(document.getElementById('hidden_textarea').scrollHeight / parseInt($('#hidden_textarea').css('line-height')));
if (lns > 10) {
$('#used_lines').css('color', '#ff0000');
}
else {
$('#used_lines').css('color', '');
}
$('#used_lines').html(lns+' lines');
});
$('#visible_textarea').change(function(){
$(this).keyup();
});

Jquery slideshow starts with an image on the left of a row but I want it to start at the right end

I'm using this javascript and the slide show slides right to left with the images in this order and positon:
start postion > 1 | 2 | 3 | 4 | 5 | 6 etc etc
but I want to swap them so they run in this position
6 | 5 | 4 | 3 | 2 | 1 < start position
Kind of like reading a book back to front, but keeping it in the right order
I've been told I need to modify the lines labelled below: //MODIFY ME
I hope someone can help! Thank you
Here's my code
(function($) {
$.fn.slideshow = function(method) {
if ( this[0][method] ) {
return this[0][ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return this.each(function() {
var ANIMATION_DURATION = .6; // The duration to flick the content. In seconds.
var MOVE_THRESHOLD = 10; // Since touch points can move slightly when initiating a click this is the
// amount to move before allowing the element to dispatch a click event.
var itemWidth;
var horizontalGap;
var $this = $(this);
var collection;
var viewItems = [];
var touchStartTransformX; // The start transformX when the user taps.
var touchStartX; // The start x coord when the user taps.
var interval; // Interval used for measuring the drag speed.
var wasContentDragged; // Flag for whether or not the content was dragged. Takes into account MOVE_THRESHOLD.
var targetTransformX; // The target transform X when a user flicks the content.
var touchDragCoords = []; // Used to keep track of the touch coordinates when dragging to measure speed.
var touchstartTarget; // The element which triggered the touchstart.
var selectedIndex = 0; // The current visible page.
var viewPortWidth; // The width of the div that holds the horizontal content.
var isAnimating;
var pageChangedLeft;
// The x coord when the items are reset.
var resetX;
var delayTimeout;
init(method);
function init(options) {
collection = options.data;
renderer = options.renderer;
itemWidth = options.itemWidth;
horizontalGap = options.horizontalGap;
initLayout();
$this[0].addEventListener("touchstart", touchstartHandler);
$this[0].addEventListener("mousedown", touchstartHandler);
viewPortWidth = $this.width();
$this.on("webkitTransitionEnd", transitionEndHandler);
collection.on("add", addItem);
}
// MODIFY ME
function initLayout() {
// Layout five items. The one in the middle is always the selected one.
for (var i = 0; i < 5; i++) {
var viewItem;
if (i > 1 && collection.at(i - 2)) // Start at the one in the middle. Subtract 2 so data index starts at 0.
viewItem = new renderer({model: collection.at(i - 2)});
else
viewItem = new renderer();
viewItem.render().$el.appendTo($this);
viewItem.$el.css("left", itemWidth * i + horizontalGap * i);
viewItem.setState(i != 2 ? "off" : "on");
viewItems.push(viewItem);
}
// Center the first viewItem
resetX = itemWidth * 2 - ($this.width() - itemWidth - horizontalGap * 4) / 2;
setTransformX(-resetX);
}
function getCssLeft($el) {
var left = $el.css("left");
return Number(left.split("px")[0]);
}
// MODIFY ME
function transitionEndHandler() {
if (pageChangedLeft != undefined) {
var viewItem;
if (pageChangedLeft) {
// Move the first item to the end.
viewItem = viewItems.shift();
viewItems.push(viewItem);
viewItem.model = collection.at(selectedIndex + 2);
viewItem.$el.css("left", getCssLeft(viewItems[3].$el) + itemWidth + horizontalGap);
} else {
// Move the last item to the beginning.
viewItem = viewItems.pop();
viewItems.splice(0, 0, viewItem);
viewItem.model = collection.at(selectedIndex - 2);
viewItem.$el.css("left", getCssLeft(viewItems[1].$el) - itemWidth - horizontalGap);
}
viewItem.render();
// Reset the layout of the items.
for (var i = 0; i < 5; i++) {
var viewItem = viewItems[i];
viewItem.$el.css("left", itemWidth * i + horizontalGap * i);
viewItem.setState(i != 2 ? "off" : "on");
}
// Reset the transformX so we don't run into any rendering limits. Can't find a definitive answer for what the limits are.
$this.css("-webkit-transition", "none");
setTransformX(-resetX);
pageChangedLeft = undefined;
}
}
function touchstartHandler(e) {
clearInterval(interval);
wasContentDragged = false;
transitionEndHandler();
// Prevent the default so the window doesn't scroll and links don't open immediately.
e.preventDefault();
// Get a reference to the element which triggered the touchstart.
touchstartTarget = e.target;
// Check for device. If not then testing on desktop.
touchStartX = window.Touch ? e.touches[0].clientX : e.clientX;
// Get the current transformX before the transition is removed.
touchStartTransformX = getTransformX();
// Set the transformX before the animation is stopped otherwise the animation will go to the end coord
// instead of stopping at its current location which is where the drag should begin from.
setTransformX(touchStartTransformX);
// Remove the transition so the content doesn't tween to the spot being dragged. This also moves the animation to the end.
$this.css("-webkit-transition", "none");
// Create an interval to monitor how fast the user is dragging.
interval = setInterval(measureDragSpeed, 20);
document.addEventListener("touchmove", touchmoveHandler);
document.addEventListener("touchend", touchendHandler);
document.addEventListener("mousemove", touchmoveHandler);
document.addEventListener("mouseup", touchendHandler);
}
function measureDragSpeed() {
touchDragCoords.push(getTransformX());
}
function touchmoveHandler(e) {
var deltaX = (window.Touch ? e.touches[0].clientX : e.clientX) - touchStartX;
if (wasContentDragged || Math.abs(deltaX) > MOVE_THRESHOLD) { // Keep track of whether or not the user dragged.
wasContentDragged = true;
setTransformX(touchStartTransformX + deltaX);
}
}
function touchendHandler(e) {
document.removeEventListener("touchmove", touchmoveHandler);
document.removeEventListener("touchend", touchendHandler);
document.removeEventListener("mousemove", touchmoveHandler);
document.removeEventListener("mouseup", touchendHandler);
clearInterval(interval);
e.preventDefault();
if (wasContentDragged) { // User dragged more than MOVE_THRESHOLD so transition the content.
var previousX = getTransformX();
var bSwitchPages;
// Compare the last 5 coordinates
for (var i = touchDragCoords.length - 1; i > Math.max(touchDragCoords.length - 5, 0); i--) {
if (touchDragCoords[i] != previousX) {
bSwitchPages = true;
break;
}
}
// User dragged more than halfway across the screen.
if (!bSwitchPages && Math.abs(touchStartTransformX - getTransformX()) > (viewPortWidth / 2))
bSwitchPages = true;
if (bSwitchPages) {
if (previousX > touchStartTransformX) { // User dragged to the right. go to previous page.
if (selectedIndex > 0) { // Make sure user is not on the first page otherwise stay on the same page.
selectedIndex--;
tweenTo(touchStartTransformX + itemWidth + horizontalGap);
pageChangedLeft = false;
} else {
tweenTo(touchStartTransformX);
pageChangedLeft = undefined;
}
} else { // User dragged to the left. go to next page.
if (selectedIndex + 1 < collection.length) {// Make sure user is not on the last page otherwise stay on the same page.
selectedIndex++;
tweenTo(touchStartTransformX - itemWidth - horizontalGap);
pageChangedLeft = true;
} else {
tweenTo(touchStartTransformX);
pageChangedLeft = undefined;
}
}
} else {
tweenTo(touchStartTransformX);
pageChangedLeft = undefined;
}
} else { // User dragged less than MOVE_THRESHOLD trigger a click event.
var event = document.createEvent("MouseEvents");
event.initEvent("click", true, true);
touchstartTarget.dispatchEvent(event);
}
}
// Returns the x of the transform matrix.
function getTransformX() {
var transformArray = $this.css("-webkit-transform").split(","); // matrix(1, 0, 0, 1, 0, 0)
var transformElement = $.trim(transformArray[4]); // remove the leading whitespace.
return transformX = Number(transformElement); // Remove the ).
}
// Sets the x of the transform matrix.
function setTransformX(value) {
$this.css("-webkit-transform", "translateX("+ Math.round(value) + "px)");
}
function tweenTo(value) {
isAnimating = true;
targetTransformX = value;
// Set the style for the transition.
$this.css("-webkit-transition", "-webkit-transform " + ANIMATION_DURATION + "s");
// Need to set the timing function each time -webkit-transition is set.
// The transition is set to ease-out.
$this.css("-webkit-transition-timing-function", "cubic-bezier(0, 0, 0, 1)");
setTransformX(targetTransformX);
}
// MODIFY ME
function addItem(folio) {
clearTimeout(delayTimeout);
// Create a timeout in case multiple items are added in the same frame.
// When the timeout completes all of the view items will have their model
// updated. The renderer should check to make sure the model is different
// before making any changes.
delayTimeout = setTimeout(function(folio) {
var index = collection.models.indexOf(folio);
var dataIndex = index;
var firstIndex = selectedIndex - 2;
var dataIndex = firstIndex;
var viewItem;
for (var i = 0; i < viewItems.length; i++) {
viewItem = viewItems[i];
if (dataIndex >= 0 && dataIndex < collection.length) {
viewItem.model = collection.at(dataIndex);
viewItem.render();
}
viewItem.setState(i != 2 ? "off" : "on");
dataIndex += 1;
}
}, 200);
}
// Called when the data source has changed. Resets the view with the new data source.
this.setData = function(data) {
$this.empty();
viewItems = [];
collection = data;
selectedIndex = 0;
initLayout();
}
});
} else {
$.error( 'Method ' + method + ' does not exist on Slideshow' );
}
}
})(jQuery);
From what I can make out, you need to simply "flip" the loops that create the sides in the slideshow so that it makes the last slide where it was making the first. It seems to do this in two places.
Then, you will need to amend the code which adds a slide to make it add it before the other slides instead of after.
This sounds an awful lot like homework - it's always best to attempt an answer before asking on here. An example on a site like JSFiddle is also generally appreciated.

Find out the 'line' (row) number of the cursor in a textarea

I would like to find out and keep track of the 'line number' (rows) of the cursor in a textarea. (The 'bigger picture' is to parse the text on the line every time a new line is created/modified/selected, if of course the text was not pasted in. This saves parsing the whole text un-necessarily at set intervals.)
There are a couple of posts on StackOverflow however none of them specifically answer my question, most questions are for cursor position in pixels or displaying lines numbers besides the textarea.
My attempt is below, it works fine when starting at line 1 and not leaving the textarea. It fails when clicking out of the textarea and back onto it on a different line. It also fails when pasting text into it because the starting line is not 1.
My JavaScript knowledge is pretty limited.
<html>
<head>
<title>DEVBug</title>
<script type="text/javascript">
var total_lines = 1; // total lines
var current_line = 1; // current line
var old_line_count;
// main editor function
function code(e) {
// declare some needed vars
var keypress_code = e.keyCode; // key press
var editor = document.getElementById('editor'); // the editor textarea
var source_code = editor.value; // contents of the editor
// work out how many lines we have used in total
var lines = source_code.split("\n");
var total_lines = lines.length;
// do stuff on key presses
if (keypress_code == '13') { // Enter
current_line += 1;
} else if (keypress_code == '8') { // Backspace
if (old_line_count > total_lines) { current_line -= 1; }
} else if (keypress_code == '38') { // Up
if (total_lines > 1 && current_line > 1) { current_line -= 1; }
} else if (keypress_code == '40') { // Down
if (total_lines > 1 && current_line < total_lines) { current_line += 1; }
} else {
//document.getElementById('keycodes').innerHTML += keypress_code;
}
// for some reason chrome doesn't enter a newline char on enter
// you have to press enter and then an additional key for \n to appear
// making the total_lines counter lag.
if (total_lines < current_line) { total_lines += 1 };
// putput the data
document.getElementById('total_lines').innerHTML = "Total lines: " + total_lines;
document.getElementById('current_line').innerHTML = "Current line: " + current_line;
// save the old line count for comparison on next run
old_line_count = total_lines;
}
</script>
</head>
<body>
<textarea id="editor" rows="30" cols="100" value="" onkeydown="code(event)"></textarea>
<div id="total_lines"></div>
<div id="current_line"></div>
</body>
</html>
You would want to use selectionStart to do this.
<textarea onkeyup="getLineNumber(this, document.getElementById('lineNo'));" onmouseup="this.onkeyup();"></textarea>
<div id="lineNo"></div>
<script>
function getLineNumber(textarea, indicator) {
indicator.innerHTML = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
}
</script>
This works when you change the cursor position using the mouse as well.
This is tough because of word wrap. It's a very easy thing to count the number of line breaks present, but what happens when the new row is because of word wrap? To solve this problem, it's useful to create a mirror (credit: github.com/jevin). Here's the idea:
Create a mirror of the textarea
Send the content from the beginning of the textarea to the cursor to the mirror
Use the height of the mirror to extract the current row
On JSFiddle
jQuery.fn.trackRows = function() {
return this.each(function() {
var ininitalHeight, currentRow, firstIteration = true;
var createMirror = function(textarea) {
jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
}
var sendContentToMirror = function (textarea) {
mirror.innerHTML = String(textarea.value.substring(0,textarea.selectionStart-1)).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br />') + '.<br/>.';
calculateRowNumber();
}
var growTextarea = function () {
sendContentToMirror(this);
}
var calculateRowNumber = function () {
if(firstIteration){
ininitalHeight = $(mirror).height();
currentHeight = ininitalHeight;
firstIteration = false;
} else {
currentHeight = $(mirror).height();
}
// Assume that textarea.rows = 2 initially
currentRow = currentHeight/(ininitalHeight/2) - 1;
//remove tracker in production
$('.tracker').html('Current row: ' + currentRow);
}
// Create a mirror
var mirror = createMirror(this);
// Style the mirror
mirror.style.display = 'none';
mirror.style.wordWrap = 'break-word';
mirror.style.whiteSpace = 'normal';
mirror.style.padding = jQuery(this).css('padding');
mirror.style.width = jQuery(this).css('width');
mirror.style.fontFamily = jQuery(this).css('font-family');
mirror.style.fontSize = jQuery(this).css('font-size');
mirror.style.lineHeight = jQuery(this).css('line-height');
// Style the textarea
this.style.overflow = "hidden";
this.style.minHeight = this.rows+"em";
var ininitalHeight = $(mirror).height();
// Bind the textarea's event
this.onkeyup = growTextarea;
// Fire the event for text already present
// sendContentToMirror(this);
});
};
$(function(){
$('textarea').trackRows();
});
This worked for me:
function getLineNumber(textarea) {
return textarea.value.substr(0, textarea.selectionStart) // get the substring of the textarea's value up to the cursor position
.split("\n") // split on explicit line breaks
.map((line) => 1 + Math.floor(line.length / textarea.cols)) // count the number of line wraps for each split and add 1 for the explicit line break
.reduce((a, b) => a + b, 0); // add all of these together
};
Inspired by colab's answer as a starting point, this includes the number of word wraps without having to introduce a mirror (as in bradbarbin's answer).
The trick is simply counting how many times the number of columns textarea.cols can divide the length of each segment between explicit line breaks \n.
Note: this starts counting at 1.

Categories