I'm trying to add a simple "Read more" button to a page. I managed to prepare a working function that hides the maximum characters within an element. And to call the function I've prepared a button in the HTML with a "onclick" event to call the function.
Now a couple things happen that shouldn't. The button should reveal the hidden text but the opposite happens and everytime the button is clicked more characters are removed until I'm left with only the dots even though I have a var that limits the characters to 15. I have spent too much time trying to figure this out knowing this is probably basic jQuery. I apologize if that is the case I just hope someone can point out what I'm doing wrong here.
HTML:
<button class="content-toggle" onclick="textCollapse()">Read more</button>
jQuery:
function textCollapse() {
var limit = 15;
var chars = jQuery(".field-content p").text();
if (chars.length > limit) {
var visiblePart = jQuery("<span class='visiblepart'> "+ chars.substr(0, limit) +"</span>");
var dots = jQuery("<span class='dots'>... </span>");
jQuery(".field-content p").toggle()
.empty()
.append(visiblePart)
.append(dots)
}
};
I would save full text content into outer variable and keep it there. There is no need to requery text content every time a button is clicked. Also, it's better to have a variable that would store a state for the text - collapsed or not instead of comparing each time.
var fullText = jQuery(".field-content p").text();
var collapsed = false;
function textCollapse() {
var limit = 15;
if (collapsed) {
var visiblePart = jQuery("<span class='visiblepart'> " + fullText + "</span>");
jQuery(".field-content p")
.empty()
.append(visiblePart)
collapsed = false;
} else {
var visiblePart = jQuery("<span class='visiblepart'> " + fullText.substr(0, limit) + "</span>");
var dots = jQuery("<span class='dots'>... </span>");
jQuery(".field-content p")
.empty()
.append(visiblePart)
.append(dots)
collapsed = true;
}
}
Also, I don't think you need to call toggle() since all operations are performed synchronously and there's no need to hide p element before emptying and appending nodes.
Related
I need to split an element after user clicks on it and the attr 'contenteditable' becomes 'true'. This fiddle works for the first paragraph but not second because the latter is in a p tag. Similary in this fiddle you will see that when the element has html tags in it, the counter loses accuracy and hence the text before and after the cursor is not what you'd expect.
The assumption here is that the users will split the data in a way that the help tags will stay intact. As pointed out by dandavis here, e.g. the div has <i>Hello</i> <b>Wo*rld</b>, the user will only need to split the div into two divs, first will have <i>Hello</i> and the second div will have <b>Wo*rld</b> in it.
Html:
<div><mark>{DATE}</mark><i>via email: </i><mark><i>{EMAIL- BROKER OR TENANT}</i></mark></div>
JS:
var $splitbut = $('<p class="split-but">Split</p>');
$(this).attr('contenteditable', 'true').addClass('editing').append($splitbut);
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
var start = userSelection.anchorOffset;
var end = userSelection.focusOffset;
var before = $(this).html().substr(0, start);
var after = $(this).html().substr(start, $(this).html().length);
The "Split" button is not working as generating the html is not an issue once I get proper "after" and "before" text. Any ideas as to what I am doing wrong here?
Something like this could work for the specific case you describe
$('div, textarea').on('click', function(e) {
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
var start = userSelection.anchorOffset,
end = userSelection.focusOffset,
node = userSelection.anchorNode,
allText = $(this).text(),
nodeText = $(node).text();
// before and after inside node
var nodeBefore = nodeText.substr(0, start);
var nodeAfter = nodeText.substr(start, nodeText.length);
// before and after for whole of text
var allExceptNode = allText.split(nodeText),
before = allExceptNode[0] + nodeBefore,
after = nodeAfter + allExceptNode[1];
console.log('Before: ', before);
console.log('------');
console.log('After: ', after);
});
Updated demo at https://jsfiddle.net/gaby/vaLz55fv/10/
It might exhibit issues if there are tags whose content is repeated in the whole text. (problem due to splitting)
I've made this tinymce fiddle to show what I say.
Highlight text in the editor, then click on the input text, highlight in tinyMCE is lost (obviously).
Now, I know it's not easy since both, the inline editor and the input text are in the same document, thus, the focus is only one. But is there any tinymce way to get like an "unfocused" highlight (gray color) whenever I click in an input text?
I'm saying this because I have a customized color picker, this color picker has an input where you can type in the HEX value, when clicking OK it would execCommand a color change on the selected text, but it looks ugly because the highlight is lost.
I don't want to use an iframe, I know that by using the non-inline editor (iframe) is one of the solutions, but for a few reasons, i can't use an iframe text editor.
Any suggestion here? Thanks.
P.S: Out of topic, does any of you guys know why I can't access to tinymce object in the tinyMCE Fiddle ? looks like the tinyMCE global var was overwritten by the tinymce select dom element of the page itself. I can't execute a tinyMCE command lol.
Another solution:
http://fiddle.tinymce.com/sBeaab/5
P.S: Out of topic, does any of you guys know why I can't access to
tinymce object in the tinyMCE Fiddle ? looks like the tinyMCE global
var was overwritten by the tinymce select dom element of the page
itself. I can't execute a tinyMCE command lol.
Well, you can access the tinyMCE variable and even execute commands.
this line is wrong
var colorHex = document.getElementById("colorHex")
colorHex contains input element, not value.
var colorHex = document.getElementById("colorHex").value
now it works ( neolist couldn't load, so I removed it )
http://fiddle.tinymce.com/DBeaab/1
I had to do something similar recently.
First off, you can't really have two different elements "selected" simultaneously. So in order to accomplish this you're going to need to mimic the browser's built-in 'selected text highlight'. To do this, you're going to have to insert spans into the text to simulate highlighting, and then capture the mousedown and mouseup events.
Here's a fiddle from StackOverflow user "fullpipe" which illustrates the technique I used.
http://jsfiddle.net/fullpipe/DpP7w/light/
$(document).ready(function() {
var keylist = "abcdefghijklmnopqrstuvwxyz123456789";
function randWord(length) {
var temp = '';
for (var i=0; i < length; i++)
temp += keylist.charAt(Math.floor(Math.random()*keylist.length));
return temp;
}
for(var i = 0; i < 500; i++) {
var len = Math.round(Math.random() * 5 + 3);
document.body.innerHTML += '<span id="'+ i +'">' + randWord(len) + '</span> ';
}
var start = null;
var end = null;
$('body').on('mousedown', function(event) {
start = null;
end = null;
$('span.s').removeClass('s');
start = $(event.target);
start.addClass('s');
});
$('body').on('mouseup', function(event) {
end = $(event.target);
end.addClass('s');
if(start && end) {
var between = getAllBetween(start,end);
for(var i=0, len=between.length; i<len;i++)
between[i].addClass('s');
alert('You select ' + (len) + ' words');
}
});
});
function getAllBetween(firstEl,lastEl) {
var firstIdx = $('span').index($(firstEl));
var lastIdx = $('span').index($(lastEl));
if(lastIdx == firstIdx)
return [$(firstEl)];
if(lastIdx > firstIdx) {
var firstElement = $(firstEl);
var lastElement = $(lastEl);
} else {
var lastElement = $(firstEl);
var firstElement = $(lastEl);
}
var collection = new Array();
collection.push(firstElement);
firstElement.nextAll().each(function(){
var siblingID = $(this).attr("id");
if (siblingID != $(lastElement).attr("id")) {
collection.push($(this));
} else {
return false;
}
});
collection.push(lastElement);
return collection;
}
As you can see in the fiddle, the gibberish text in the right pane stays highlighted regardless of focus elsewhere on the page.
At that point, you're going to have to apply your color changes to all matching spans.
I have a bit of javascript / jquery that upon pressing "enter" in a search box goes through all table rows and then hides those which are not applicable/ do not contain the string. This process is however is extremely taxing on slower systems and so I decided to implement a small loading gif so people know something is happening even though it seems the browser has frozen. The problem though is that the image never appears. I'm assuming it's because the browser freezes. So, now to my question. How can i either make the loop faster, use less computing power, and show the gif? Thank you very much
var $rows = $('tbody tr.visall');
$('#search').keydown(function(e) {
if (e.keyCode == 13){
$('.load').show();
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function () {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
};
$('.load').hide();
});
edit: this code goes through about 9000-10000 tr elements.
Hard to say if it will be enough for your data. But here is what you can do:
Reduce the amount of dom manipulations. You can add|remove class hidden to show hide rows.
If your text is static you can create text cache and do not extract it on every search.
You can use setTimeout to delay search execution to show loading gif. Not even sure you will need one. Searching in memory is quite fast.
Demo.
Code
$(function() {
var table = $('#mytable'), //your table
rows = table.find('tr').map(function(){ //all rows you need
return $(this);
}),
rowsCache = (function(from){ //text cache
return from.map(function(){
return this.text();
});
}(rows));
function delay(func) { //delayed function executor
setTimeout(func, 13);
}
var load = $('#load'); //your loader
$('#search').keydown(function(e){
var val;
if(e.keyCode === 13) {
val = $.trim($(this).val());
load.show();
table.hide(); //release dom
delay(function() {
//search in text cache
var toShow = rowsCache.map(function(_, row) {
return row.indexOf(val) > -1;
});
rows.each(function(i){
//simply toggle class let css work for you
this.toggleClass('hidden', !toShow[i]);
});
load.hide();
table.show();
});
}
});
});
Personally I would say drop the filter and use CSS selectors instead then it's just basic DOM manipulation which jQuery should be optimized for.
See http://api.jquery.com/contains-selector/ for documentation and here is a little fiddle http://jsfiddle.net/tgz5X/
Here is what I use in the fiddle as a simple example
function search(mySearchValue) {
$("tr > td:contains(" + mySearchValue + ")").show();
$("tr > td:not(:contains(" + mySearchValue + "))").hide();
}
You would need more but you should get the idea behind this approach.
Or use filter still but with selectors, also this may trigger hide on element which will have show after so more DOM manipualtion
$("tr td").hide().filter(":contains(" + mySearchValue + ")").show();
I've known how to use the document.selection to do the highlighting. For example
/* http://jsfiddle.net/4J2dy/ */
$("#content").on('mouseup', function() {
highlighting();
});
var highlighting = function () {
var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection();
if(seleted_str != "") {
var stringToBeHighlighted = seleted_str.getRangeAt(0);
var span = document.createElement("span");
span.style.cssText = "background-color: #80deea";
span.className = "MT";
stringToBeHighlighted.surroundContents(span);
}
};
But there is something I don't know how to achieve.
Let's say that I have four layers created with the same content at the same time.
And I would like to select a sentence on the controlling layer while all the same sentence in the other three layers will be selected too.(See image below)
After the selection, I would like to pop out a menu(which I can do), and get the DOM element based on which button is pressed.(See image below)
Could anyone tell me how to achieve this? Or it just can't be done? I would be grateful if someone could answer for me.
It's kind of possible, and I would appreciate the input of SO user Tim Down as he knows a lot about JS Range/Selections, but I'll present my partial solution already.
Instead of selecting the 4 layers, you could just store the startOffset & endOffset in an external object that is updated on mouseup. The only by-effect this has, is that the user's selection will only get the color of the span when they click a layer button.
The advantage is that you can now simply work with DOM Textnodes as opposed to ranges/ selection (more complex, to me anyway).
I've chosen to find the layers with a data-layer attribute on the buttons and a corresponding id on the layers themselves. I handled the 'appending' of the 'selected span' by slicing the text content of the text nodes in the layers, like so:
layer.innerHTML = txt.slice(0, selText.start)
+ '<span class="MT" style="background-color: #80deea">'
+ txt.slice(selText.start, selText.end) + '</span>'
+ txt.slice(selText.end, txt.length);
See it in action here. I've added a cleanSelection function so only one selection is possible at a time (the start & end counters fail because selection ranges don't take into account HTML tags, so you have to get rid of the spans).
Final notes:
The fiddle will not work in browsers not supporting getElementsByClassName
The fiddle only supports one selection at a time.
The fiddle does not extensively test all conditions (eg, whether the nodetype of the first child is truly a text node, etc. But it ain't hard to add that yourself)
Entire JS code as reference (also in fiddle):
// this object will hold the start & end offsets of selection value
var selText = false;
// selText will be updated on mouseup
document.body.onmouseup = getSelText;
// on button click, selText will be highlighted
document.body.onclick = function(e) {
var target = e.target || e.srcElement, range, layer, txt;
// only do if it's a layer button & the selection is non-empty
if (target.getAttribute('data-layer') && selText !== false) {
// first remove previous spans, they break the startOffset & endOffset of the selection range
cleanSelection();
// get the clicked layer
layer = document.getElementById(target.getAttribute('data-layer'));
// this assumes that the first node in the layer is a textNode
txt = layer.firstChild.nodeValue;
// let's append the selection container now
layer.innerHTML = txt.slice(0, selText.start)
+ '<span class="MT" style="background-color: #80deea">'
+ txt.slice(selText.start, selText.end) + '</span>'
+ txt.slice(selText.end, txt.length);
// ...and empty the 'real selection'
window.getSelection().collapse();
// log results to console
console.log('From char ' + selText.start + ' to char ' + selText.end + ', in ' + layer.id);
}
};
function getSelText () {
var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection(), stringToBeHighlighted;
if(seleted_str !== "") {
stringToBeHighlighted = seleted_str.getRangeAt(0);
selText = {
start: stringToBeHighlighted.startOffset,
end: stringToBeHighlighted.endOffset
};
} else {
selText = false;
}
}
function cleanSelection() {
var getText, mtSpan = document.getElementsByClassName('MT');
for ( var i = 0; i < mtSpan.length; i++) {
getText = mtSpan[i].innerHTML;
mtSpan[i].previousSibling.nodeValue = mtSpan[i].previousSibling.nodeValue + getText + mtSpan[i].nextSibling.nodeValue;
mtSpan[i].parentNode.removeChild(mtSpan[i].nextSibling);
mtSpan[i].parentNode.removeChild(mtSpan[i]);
}
}
So I have two divs and inside there gona be some blocks:
<div class="list-block 01">
<span>21#epos.com</span>
<span class="moveSym" id="01">+</span>
</div>
if one clicks on
+
whole block moves to other div.
Everything works but only to move ech block to another div once,
but I need them to go both ways as much as .moveSym clicked.
my JS
//remove block on click
$('.del-block').on('click', function() {
var block = $(this).attr('id');
$('.' + block).remove();
})
//move form list blocks to different fields
$('.leftSide01 .moveSym').click(function() {
console.log($(this).attr('id'));
var id = $(this).attr('id');
$(this).text("-");
$('.leftSide01 .list-block.' + id).appendTo('.rightSide01');
})
$('.rightSide01 .moveSym').click(function() {
console.log($(this).attr('id'));
var id = $(this).attr('id');
$(this).text("+");
$('.rightSide01 .list-block.' + id).appendTo('.leftSide01');
})
I know there are plugins for this, but I really want to write it by myself and learn :)
Fiddle http://jsfiddle.net/A1ex5andr/CRvVK/
Need to use event delegation, because the handler to be executed depends on the parent element.
//move form list blocks to different fields
$('.leftSide01').on('click', '.moveSym', function () {
console.log($(this).attr('id'));
var id = $(this).attr('id');
$(this).text("-");
$('.leftSide01 .list-block.' + id).appendTo('.rightSide01');
})
$('.rightSide01').on('click', '.moveSym', function () {
console.log($(this).attr('id'));
var id = $(this).attr('id');
$(this).text("+");
$('.rightSide01 .list-block.' + id).appendTo('.leftSide01');
})
Demo: Fiddle
You can really simplify this logic into one function that works for both (if there are only ever going to be two divs) . . .
$('.moveSym').click(function() {
console.log($(this).attr('id')); // I just left in, because you had it in the original code :)
var targetParent = $(".rightSide01");
var linkText = "-";
if ($(this).parent(".rigthSide01") > 0) {
linkText = "+";
targetParent = $(".leftSide01");
}
$(this).text(linkText);
$(this).parent().appendTo(targetParent);
});
This code starts out assuming that the block is on the left-hand side . . . it sets up the targetParent value (i.e., where the block will move to) to the right-hand side and the new link text to be "-".
After that, it checks to see if the block is actually on the right-hand side, instead, and, if it is, then it updates the variables with the values needed to move it to the left.
At that point, it updates the text in the "move-sym" span element to the final linkText value, and moves its parent block to the new target div (the targetParent value).
No need to worry about the delegation or event handlers in this one, because the function is the same, regardless of the location, and will travel with the "move-sym" span element, wherever it goes.