Real counter with contenteditable - javascript

I'm already looking for a solution for several days to add a character counter associated with my directive contenteditable.
Unfortunately I can not find a solution.
To highlight the problem different browsers have different behaviors with contenteditable.
Here is the directive I'm using :
var app = angular.module("App", []);
app.directive("contenteditable", function($timeout) {
return {
restrict: 'A',
require : ['^?ngModel'],
link : function(scope, element, attrs, args){
var ngModel = args[0];
if (ngModel === null) {
return null;
}
var modelKey = getModelKey();
opts = {
onlyText: false,
convertNewLines: false,
noLf: false,
};
angular.forEach(['onlyText', 'convertNewLines', 'noLf'], function (opt) {
if (attrs.hasOwnProperty(opt) && attrs[opt] && attrs[opt] !== 'false') {
opts[opt] = true;
}
});
$timeout(function () {
return (opts.onlyText && opts.noLf) ? element.text(ngModel.$modelValue) : element.html(ngModel.$modelValue);
});
var validate = function (content) {
var length = content.length;
refreshFn = function(content) {
if (content == undefined || content == null) {
content = '';
}
scope.maxCharacter= attrs.ngMaxlength;
scope.remaining = (length);
};
scope.$watch('ngModel', function(content){
refreshFn(content);
}, true);
if (length > attrs.ngMaxlength) {
ngModel.$setValidity(modelKey, false);
return element.addClass('-error');
}
if (element.hasClass('-error')) {
ngModel.$setValidity(modelKey, true);
return element.removeClass('-error');
}
};
var read = function () {
var content = '';
if ((opts.onlyText && opts.noLf)) {
content = element.text();
} else {
content = element.html();
if (content) {
content = parseHtml(content);
}
}
if (content !== '') {
content = content.replace(/ /g, '');
content = content.trim();
}
ngModel.$setViewValue(content);
validate(content);
};
ngModel.$render = function () {
if ((opts.onlyText && opts.noLf)) {
element.text(ngModel.$viewValue || '');
} else {
element.html(ngModel.$viewValue || '');
}
};
element.bind('blur keyup change focus', function (event) {
scope.$apply(read);
scope.displayCount = true;
if (event.type === 'blur') {
scope.$apply(ngModel.$render);
}
});
function getModelKey() {
if (typeof attrs.ngModel === 'undefined') {
return null;
}
var split = attrs.ngModel.split('.');
return split[split.length-1];
}
function parseHtml(html) {
html = html.replace(/ /g, '');
if (opts.convertNewLines || opts.noLf) {
var lf = '\r\n',
rxl = /\r\n$/;
if (opts.noLf) {
lf = ' ';
rxl = / $/;
}
html = html.replace(/<br(\s*)\/*>/ig, ''); // replace br for newlines
html = html.replace(/<[div>]+>/ig, lf); // replace div for newlines
html = html.replace(/<\/[div>]+>/gm, ''); // remove remaining divs
html = html.replace(/ /g, '');
html = html.replace(/<[p>]+>/ig, lf); // replace p for newlines
html = html.replace(/<\/[p>]+>/gm, ''); // remove remaining p
html = html.replace(/<[span>]+>/ig, lf); // replace p for newlines
html = html.replace(/<\/[span>]+>/gm, ''); // remove remaining p
html = html.replace(rxl, ''); // remove last newline
html = _.unescape(html); // replaces &, <, >, ", ` and ' with their unescaped counterparts.
}
if (opts.onlyText) {
html = html.replace(/<\S[^><]*>/g, '');
}
return html;
}
}
};
});
and the html file :
<div class="content-editable" contenteditable="true"
ng-model="line.value"
ng-maxlength=255
only-text="true"
convert-new-lines="true"
no-lf="false">
</div>
<div ng-if="displayCount" class="ctn" ng-if="countChar">
<span class="numberChar">
<span class="italic" style="margin-right:5px;">
<span ng-class="remaining>=maxCharacter ? 'errorMsg': ''">
{{remaining}}
</span> / {{maxCharacter}}
</span>
<span class="errorMsg error" ng-if="remaining>=maxCharacter"></span>
</span>
</div>
</div>
the example on codepen : https://codepen.io/gregand/pen/yLBjWYW
On Chrome and Firefox, when I enter 6 characters (line break included), the counter counts 9 characters
On Edge, the counter is good
I tried to change the contenteditable css of display: block to inline-block, it corrects the problem on Chrome but there are problems on Edge.
I tried to use
document.execCommand('defaultParagraphSeparator', false, 'p');
but without success.
If anyone knows a solution that works on all browsers, I would be interested

I found the solution, in fact the problem came from the next line :
var lf = '\r\n'
I ask each tag of the replaced by the variable lf
html = html.replace(/<[div>]+>/ig, lf);
'\r\n' counts two characters, one for '\r' and another for '\n'
whereas the line break should only have one character.
For the solution I therefore replace the variable lf
var lf = '\n'
and it works multi browsers
https://codepen.io/gregand/pen/yLBjWYW

Related

How to get the highest level parent of clicked element [duplicate]

HTML
<body>
<div class="lol">
<a class="rightArrow" href="javascriptVoid:(0);" title"Next image">
</div>
</body>
Pseudo Code
$(".rightArrow").click(function() {
rightArrowParents = this.dom(); //.dom(); is the pseudo function ... it should show the whole
alert(rightArrowParents);
});
Alert message would be:
body div.lol a.rightArrow
How can I get this with javascript/jquery?
Here is a native JS version that returns a jQuery path. I'm also adding IDs for elements if they have them. This would give you the opportunity to do the shortest path if you see an id in the array.
var path = getDomPath(element);
console.log(path.join(' > '));
Outputs
body > section:eq(0) > div:eq(3) > section#content > section#firehose > div#firehoselist > article#firehose-46813651 > header > h2 > span#title-46813651
Here is the function.
function getDomPath(el) {
var stack = [];
while ( el.parentNode != null ) {
console.log(el.nodeName);
var sibCount = 0;
var sibIndex = 0;
for ( var i = 0; i < el.parentNode.childNodes.length; i++ ) {
var sib = el.parentNode.childNodes[i];
if ( sib.nodeName == el.nodeName ) {
if ( sib === el ) {
sibIndex = sibCount;
}
sibCount++;
}
}
if ( el.hasAttribute('id') && el.id != '' ) {
stack.unshift(el.nodeName.toLowerCase() + '#' + el.id);
} else if ( sibCount > 1 ) {
stack.unshift(el.nodeName.toLowerCase() + ':eq(' + sibIndex + ')');
} else {
stack.unshift(el.nodeName.toLowerCase());
}
el = el.parentNode;
}
return stack.slice(1); // removes the html element
}
Using jQuery, like this (followed by a solution that doesn't use jQuery except for the event; lots fewer function calls, if that's important):
$(".rightArrow").click(function () {
const rightArrowParents = [];
$(this)
.parents()
.addBack()
.not("html")
.each(function () {
let entry = this.tagName.toLowerCase();
const className = this.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
});
console.log(rightArrowParents.join(" "));
return false;
});
Live example:
$(".rightArrow").click(function () {
const rightArrowParents = [];
$(this)
.parents()
.addBack()
.not("html")
.each(function () {
let entry = this.tagName.toLowerCase();
const className = this.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
});
console.log(rightArrowParents.join(" "));
return false;
});
<div class=" lol multi ">
Click here
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
(In the live examples, I've updated the class attribute on the div to be lol multi to demonstrate handling multiple classes.)
That uses parents to get the ancestors of the element that was clicked, removes the html element from that via not (since you started at body), then loops through creating entries for each parent and pushing them on an array. Then we use addBack to add the a back into the set, which also changes the order of the set to what you wanted (parents is special, it gives you the parents in the reverse of the order you wanted, but then addBack puts it back in DOM order). Then it uses Array#join to create the space-delimited string.
When creating the entry, we trim className (since leading and trailing spaces are preserved, but meaningless, in the class attribute), and then if there's anything left we replace any series of one or more spaces with a . to support elements that have more than one class (<p class='foo bar'> has className = "foo bar", so that entry ends up being p.foo.bar).
Just for completeness, this is one of those places where jQuery may be overkill, you can readily do this just by walking up the DOM:
$(".rightArrow").click(function () {
const rightArrowParents = [];
for (let elm = this; elm; elm = elm.parentNode) {
let entry = elm.tagName.toLowerCase();
if (entry === "html") {
break;
}
const className = elm.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
}
rightArrowParents.reverse();
console.log(rightArrowParents.join(" "));
return false;
});
Live example:
$(".rightArrow").click(function () {
const rightArrowParents = [];
for (let elm = this; elm; elm = elm.parentNode) {
let entry = elm.tagName.toLowerCase();
if (entry === "html") {
break;
}
const className = elm.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
}
rightArrowParents.reverse();
console.log(rightArrowParents.join(" "));
return false;
});
<div class=" lol multi ">
Click here
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
There we just use the standard parentNode property (or we could use parentElement) of the element repeatedly to walk up the tree until either we run out of parents or we see the html element. Then we reverse our array (since it's backward to the output you wanted), and join it, and we're good to go.
I needed a native JS version, that returns CSS standard path (not jQuery), and deals with ShadowDOM. This code is a minor update on Michael Connor's answer, just in case someone else needs it:
function getDomPath(el) {
if (!el) {
return;
}
var stack = [];
var isShadow = false;
while (el.parentNode != null) {
// console.log(el.nodeName);
var sibCount = 0;
var sibIndex = 0;
// get sibling indexes
for ( var i = 0; i < el.parentNode.childNodes.length; i++ ) {
var sib = el.parentNode.childNodes[i];
if ( sib.nodeName == el.nodeName ) {
if ( sib === el ) {
sibIndex = sibCount;
}
sibCount++;
}
}
// if ( el.hasAttribute('id') && el.id != '' ) { no id shortcuts, ids are not unique in shadowDom
// stack.unshift(el.nodeName.toLowerCase() + '#' + el.id);
// } else
var nodeName = el.nodeName.toLowerCase();
if (isShadow) {
nodeName += "::shadow";
isShadow = false;
}
if ( sibCount > 1 ) {
stack.unshift(nodeName + ':nth-of-type(' + (sibIndex + 1) + ')');
} else {
stack.unshift(nodeName);
}
el = el.parentNode;
if (el.nodeType === 11) { // for shadow dom, we
isShadow = true;
el = el.host;
}
}
stack.splice(0,1); // removes the html element
return stack.join(' > ');
}
Here is a solution for exact matching of an element.
It is important to understand that the selector (it is not a real one) that the chrome tools show do not uniquely identify an element in the DOM. (for example it will not distinguish between a list of consecutive span elements. there is no positioning/indexing info)
An adaptation from a similar (about xpath) answer
$.fn.fullSelector = function () {
var path = this.parents().addBack();
var quickCss = path.get().map(function (item) {
var self = $(item),
id = item.id ? '#' + item.id : '',
clss = item.classList.length ? item.classList.toString().split(' ').map(function (c) {
return '.' + c;
}).join('') : '',
name = item.nodeName.toLowerCase(),
index = self.siblings(name).length ? ':nth-child(' + (self.index() + 1) + ')' : '';
if (name === 'html' || name === 'body') {
return name;
}
return name + index + id + clss;
}).join(' > ');
return quickCss;
};
And you can use it like this
console.log( $('some-selector').fullSelector() );
Demo at http://jsfiddle.net/gaby/zhnr198y/
The short vanilla ES6 version I ended up using:
Returns the output I'm used to read in Chrome inspector e.g body div.container input#name
function getDomPath(el) {
let nodeName = el.nodeName.toLowerCase();
if (el === document.body) return 'body';
if (el.id) nodeName += '#' + el.id;
else if (el.classList.length)
nodeName += '.' + [...el.classList].join('.');
return getDomPath(el.parentNode) + ' ' + nodeName;
};
I moved the snippet from T.J. Crowder to a tiny jQuery Plugin. I used the jQuery version of him even if he's right that this is totally unnecessary overhead, but i only use it for debugging purpose so i don't care.
Usage:
Html
<html>
<body>
<!-- Two spans, the first will be chosen -->
<div>
<span>Nested span</span>
</div>
<span>Simple span</span>
<!-- Pre element -->
<pre>Pre</pre>
</body>
</html>
Javascript
// result (array): ["body", "div.sampleClass"]
$('span').getDomPath(false)
// result (string): body > div.sampleClass
$('span').getDomPath()
// result (array): ["body", "div#test"]
$('pre').getDomPath(false)
// result (string): body > div#test
$('pre').getDomPath()
Repository
https://bitbucket.org/tehrengruber/jquery.dom.path
I've been using Michael Connor's answer and made a few improvements to it.
Using ES6 syntax
Using nth-of-type instead of nth-child, since nth-of-type looks for children of the same type, rather than any child
Removing the html node in a cleaner way
Ignoring the nodeName of elements with an id
Only showing the path until the closest id, if any. This should make the code a bit more resilient, but I left a comment on which line to remove if you don't want this behavior
Use CSS.escape to handle special characters in IDs and node names
~
export default function getDomPath(el) {
const stack = []
while (el.parentNode !== null) {
let sibCount = 0
let sibIndex = 0
for (let i = 0; i < el.parentNode.childNodes.length; i += 1) {
const sib = el.parentNode.childNodes[i]
if (sib.nodeName === el.nodeName) {
if (sib === el) {
sibIndex = sibCount
break
}
sibCount += 1
}
}
const nodeName = CSS.escape(el.nodeName.toLowerCase())
// Ignore `html` as a parent node
if (nodeName === 'html') break
if (el.hasAttribute('id') && el.id !== '') {
stack.unshift(`#${CSS.escape(el.id)}`)
// Remove this `break` if you want the entire path
break
} else if (sibIndex > 0) {
// :nth-of-type is 1-indexed
stack.unshift(`${nodeName}:nth-of-type(${sibIndex + 1})`)
} else {
stack.unshift(nodeName)
}
el = el.parentNode
}
return stack
}
All the examples from other ответов did not work very correctly for me, I made my own, maybe my version will be more suitable for the rest
const getDomPath = element => {
let templateElement = element
, stack = []
for (;;) {
if (!!templateElement) {
let attrs = ''
for (let i = 0; i < templateElement.attributes.length; i++) {
const name = templateElement.attributes[i].name
if (name === 'class' || name === 'id') {
attrs += `[${name}="${templateElement.getAttribute(name)}"]`
}
}
stack.push(templateElement.tagName.toLowerCase() + attrs)
templateElement = templateElement.parentElement
} else {
break
}
}
return stack.reverse().slice(1).join(' > ')
}
const currentElement = document.querySelectorAll('[class="serp-item__thumb justifier__thumb"]')[7]
const path = getDomPath(currentElement)
console.log(path)
console.log(document.querySelector(path))
console.log(currentElement)
var obj = $('#show-editor-button'),
path = '';
while (typeof obj.prop('tagName') != "undefined"){
if (obj.attr('class')){
path = '.'+obj.attr('class').replace(/\s/g , ".") + path;
}
if (obj.attr('id')){
path = '#'+obj.attr('id') + path;
}
path = ' ' +obj.prop('tagName').toLowerCase() + path;
obj = obj.parent();
}
console.log(path);
hello this function solve the bug related to current element not show in the path
check this now
$j(".wrapper").click(function(event) {
selectedElement=$j(event.target);
var rightArrowParents = [];
$j(event.target).parents().not('html,body').each(function() {
var entry = this.tagName.toLowerCase();
if (this.className) {
entry += "." + this.className.replace(/ /g, '.');
}else if(this.id){
entry += "#" + this.id;
}
entry=replaceAll(entry,'..','.');
rightArrowParents.push(entry);
});
rightArrowParents.reverse();
//if(event.target.nodeName.toLowerCase()=="a" || event.target.nodeName.toLowerCase()=="h1"){
var entry = event.target.nodeName.toLowerCase();
if (event.target.className) {
entry += "." + event.target.className.replace(/ /g, '.');
}else if(event.target.id){
entry += "#" + event.target.id;
}
rightArrowParents.push(entry);
// }
where $j = jQuery Variable
also solve the issue with .. in class name
here is replace function :
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function replaceAll(str, find, replace) {
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
Thanks
$(".rightArrow")
.parents()
.map(function () {
var value = this.tagName.toLowerCase();
if (this.className) {
value += '.' + this.className.replace(' ', '.', 'g');
}
return value;
})
.get().reverse().join(", ");

How to capitalize first and third letter?

I want to capitalize first letter and third letter in my textarea.
I can capitalize only first letter and then every next letters and words are transformed to lowercase.
If there is any solution for this problem, please tell me.
I am using AngularJS.
This is what im trying and did.
link: function (scope, iElement, iAttrs, controller) {
//console.log('init');
controller.$parsers.push(function (inputValue) {
var transformedInput = (!!inputValue) ? inputValue.charAt(0).toUpperCase() + inputValue.substr(1).toLowerCase() : '';
if (transformedInput != inputValue) {
controller.$setViewValue(transformedInput);
controller.$render();
}
return transformedInput;
});
This works only for first letter, it transforms to uppercase and then transforms another letter and words to lowercase.
I tried to change my code into this but nothing.
var transformedInput = (!!inputValue) ? inputValue.charAt(0).toUpperCase() + inputValue.substr(1).toLowerCase() + inputValue.charAt(3).toUpperCase() + inputValue.substr(4).toLowerCase(): '';
Have a look at this. Same as what you are doing just using for loop to identify the character index to modify.
var inputValue = "test";
var transformedInput = '';
if(inputValue){
for(var i=0; i<inputValue.length; i++){
if(i===0 || i=== 2){
transformedInput += inputValue.charAt(i).toUpperCase();
} else {
transformedInput += inputValue.charAt(i).toLowerCase();
}
}
}
console.log(transformedInput);
Here is a function to capitalize chars at specific positions
function capitalizeAtPositions(string, indexes) {
(indexes || []).forEach(function(index) {
if (string.length < index) return;
string = string.slice(0, index) +
string.charAt(index).toUpperCase() + string.slice(index+1);
});
return string;
}
Run it as follows:
var test = "abcdefg";
var result = capitalizeAtPositions(test, [0, 2]);
//AbCdefg
In your case i think it will be something like (can't test it without jsfiddle):
var transformedInput = capitalizeAtPositions(inputValue || '', [0, 2]);
My simple solution
var inputValue = 'your value';
function toUpper (str) {
var result = '';
for (var i = 0; i < str.length; i++) {
if (i === 0 || i === 2) {
result += str[i].toUpperCase();
} else {
result += str[i].toLowerCase();
}
}
return result;
}
var transformedInput = toUpper(inputValue);
Seeing how you need to have the input change as you type, you'll probably need a directive; here's a one to capitalize the given letters of any input with an ng-model:
https://plnkr.co/edit/hWhmjQWdrghvsL20l3DE?p=preview
app.directive('myUppercase', function() {
return {
scope: {
positions: '=myUppercase'
},
require: 'ngModel',
link: function(scope, elem, attrs, ngModelCtrl) {
scope.positions = scope.positions || []
function makeString(string) {
if (!string) return;
angular.forEach(scope.positions, function(pos) {
string = string.slice(0, pos) + string.slice(pos, pos+1).toUpperCase() + string.slice(pos + 1)
console.log(string)
})
return string;
}
ngModelCtrl.$parsers.push(makeString)
ngModelCtrl.$formatters.push(makeString)
}
}
})
HTML:
<input ng-model="value" my-uppercase="[0, 2]">
My solution for "McArturo" this type of text you want
$("#LastName").keyup(function () {
var last_name = $("#LastName").val();
var op = last_name.substr(0, 2);
if (op == "mc" || op == "Mc" || op == "MC") {
$("#LastName").val("Mc" + (last_name.charAt(2).toUpperCase()) + last_name.substr(3).toLowerCase());
} else {
$("#LastName").val(last_name.charAt(0).toUpperCase() + last_name.substr(1).toLowerCase());
}});

Highlight Text in textarea when a Hashtag typed followed by one or more character

I want to be able to let the user wile typing in the text area if he typed a hash-tag followed by one or more character as he type the text get highlighted till he hits space.
The thing is i want to achieve something like Facebook's new hash-tag feature, I've done the logic the coding but still not able to achieve it visually.
The approach i tried is by using Jquery as follows:
<textarea id="txtArea">Here is my #Hash</textarea>
$("#txtArea").onkeyup(function(){
var result = $("#txtArea").match(/ #[\w]+/g);
//result = '#Hash'
});
But i couldn't complete & i don't know where to go from here so any solution, advice, plugin that i could use i'll be very grateful.
I don't believe there is any way to highlight words (other than one single highlight) within a basic textarea as it does not accept markup, you could turn the textarea into a small Rich Text editor, but that seems a little overcomplicated. I would probably go for similar approach to the editor here on SO, and have a preview window below so that you can see what is being marked. You could use something like this, of course you may want to change how it works a little to suit your exact needs. It should at least give you some ideas.
CSS
#preview {
height: 2em;
width: 12em;
border-style: solid;
border-width: 1px;
}
.hashSymbol {
color: #f90;
}
HTML
<textarea id="userInput"></textarea>
<div id="preview"></div>
Javascript
/*jslint maxerr: 50, indent: 4, browser: true */
(function () {
"use strict";
function walkTheDOM(node, func) {
func(node);
node = node.firstChild;
while (node) {
walkTheDOM(node, func);
node = node.nextSibling;
}
}
function getTextNodes(element) {
var nodes = [];
walkTheDOM(element, function (node) {
if (node.nodeType === 3) {
nodes.push(node);
}
});
return nodes;
}
function escapeRegex(string) {
return string.replace(/[\[\](){}?*+\^$\\.|]/g, "\\$&");
}
function highlight(element, string, classname) {
var nodes = getTextNodes(element),
length = nodes.length,
stringLength = string.length,
rx = new RegExp("\\B" + escapeRegex(string)),
i = 0,
index,
text,
newContent,
span,
node;
while (i < length) {
node = nodes[i];
newContent = document.createDocumentFragment();
text = node.nodeValue;
index = text.search(rx);
while (index !== -1) {
newContent.appendChild(document.createTextNode(text.slice(0, index)));
text = text.slice(index + stringLength);
span = document.createElement("span");
span.className = classname;
span.appendChild(document.createTextNode(string));
newContent.appendChild(span);
index = text.search(rx);
}
newContent.appendChild(document.createTextNode(text));
node.parentNode.replaceChild(newContent, node);
i += 1;
}
}
function addEvent(elem, event, fn) {
if (typeof elem === "string") {
elem = document.getElementById(elem);
}
function listenHandler(e) {
var ret = fn.apply(null, arguments);
if (ret === false) {
e.stopPropagation();
e.preventDefault();
}
return ret;
}
function attachHandler() {
window.event.target = window.event.srcElement;
var ret = fn.call(elem, window.event);
if (ret === false) {
window.event.returnValue = false;
window.event.cancelBubble = true;
}
return ret;
}
if (elem.addEventListener) {
elem.addEventListener(event, listenHandler, false);
} else {
elem.attachEvent("on" + event, attachHandler);
}
}
function emptyNode(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
function toPreviewHighlight(e, to) {
if (typeof to === "string") {
to = document.getElementById(to);
}
var value = e.target.value,
tags = value.match(/\B#\w+/g) || [],
index = tags.length - 1,
lookup = {},
fragment,
length,
tag;
while (index >= 0) {
tag = tags[index];
if (!tag.length || tag === "#" || tag.charAt(0) !== "#" || lookup[tag]) {
tags.splice(index, 1);
} else {
lookup[tag] = true;
}
index -= 1;
}
fragment = document.createDocumentFragment();
fragment.appendChild(document.createTextNode(value));
index = 0;
length = tags.length;
while (index < length) {
tag = tags[index];
highlight(fragment, tag, "hashSymbol");
index += 1;
}
emptyNode(to);
to.appendChild(fragment);
}
addEvent("userInput", "keyup", function (e) {
toPreviewHighlight(e, "preview");
});
}());
On jsfiddle
This code is slightly modified from other questions and answers here on SO (resusing code is good)
if text contains '#' change color of '#'
How to extract value between # and space in textarea value
Using JavaScript, I want to use XPath to look for the presence of a string, then refresh the page based on that

Add space between numbers/digits and letters/characters

I have a code like this
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
options.show.apply(rowcache[i]);
noresults = false;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.loader(false);
options.onAfter();
return this;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
$(this).bind(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));
I try to figure out where and how I can make a split/add space between numbers and letters. Cause some people type for example "ip1500" and the script cant match the input with an element that is like "ip 1500". My problem ist that Im a js beginner.
I was trying and trying but i cant get it work. I also tried this
I found this spot and I think it can be done here where the everything get splitted by an " " (space):
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
Would be very nice if somebody can help me.
If you want "123abc345def" to "123 abc 345 def". The replace function may help. The code is like this.
var str = "123abc345def";
str = str.replace(/(\d+)/g, function (_, num){
console.log(num);
return ' ' + num + ' ';
});
str = str.trim();
The code you linked didn't work mainly because it's using a different programming language to javascript. In theory, it should work, but javascript does not support regular expression lookbehinds (at this present time)..
Instead, I have re-wrote that fragment of code:
prepareQuery: function (val) {
function isNotLetter(a){
return (/[0-9-_ ]/.test(a));
}
var val=val.toLowerCase().split("");
var tempArray=val.join("").split("");
var currentIndex=1;
for (var i=0;i<val.length-1;i++){
if (isNotLetter(val[i]) !== isNotLetter(val[i+1])){
tempArray.splice(i+currentIndex, 0, " ");
currentIndex++;
}
}
return tempArray.join("");
}
Since you're new to javascript, I'm going to explain what it does.
It declares a function in prepareQuery to check whether or not a string contains a letter [this can be moved somewhere else]
It then splits val into an array and copies the content of val into tempArray
An index is declared (explained later)
A loop is made, which goes through every single character in val
The if statement detects whether or not the current character (val[i] as set by the loop) is the same as the character next to it (val[i+1]).
IF either one are different to the other (ie the current character is a letter while the next isn't) then a space is added to the tempArray at that "index"
The index is incremented and used as an offset in #6
The loop finishes, joins the "array" into a string and outputs the result.
DEMO:
http://jsbin.com/ebitus/1/edit
(JSFiddle was down....)
EDIT:
Sorry, but I completely misinterpreted your question... You failed to mention that you were using "quicksearch" and jQuery. In that case I'm assuming that you have a list of elements that have names and you want to search through them with the plugin...
A much easier way to match the user's query (if there is no space) is to strip the space from the search table along with the query itself - though original reverse method will work (just not as efficiently) [aka: expanding the user's query]
In this case, stripping the space from both the search table and user input would be a better method
prepareQuery: function (val) {
return val.toLowerCase().replace(/ /ig,'').split(" ");
},
testQuery: function (query, txt, _row) {
txt=txt.toLowerCase().replace(/ /ig,'');
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
DEMO:
http://jsfiddle.net/q9k9Y/3/
Edit 2:
It seems like your real intent is to create a fully functioning search feature on your website, not to just add spaces between letters and numbers. With this, I suggest using Quicksilver. I would love to work out an algorithm to extend quickSearcher but at the current time I cannot (timezones). Instead, I suggest using Quicksilver
http://jsbin.com/oruhet/12/

Get selected text in textarea [duplicate]

This question already has answers here:
How can I get the selected text in a textarea? [duplicate]
(6 answers)
Understanding what goes on with textarea selection with JavaScript
(4 answers)
Closed 10 months ago.
I'd like to store selected text in a variable and then delete the selected text by pressing a button. Preferably with jQuery, but I don't mind basic JavaScript.
I've tried the example that pointed to stripping down the code to what I need, but I can't get it to work on click for the button. It only works if I change #addchapter to textarea. what’s wrong with my code?
<html>
<head>
<script type="text/javascript" src="jquery-latest.pack.js"></script>
<script type="text/javascript" src="jquery-fieldselection.js"></script>
<script type="text/javascript"><!--//--><![CDATA[//><!--
$(document).ready(function(){
$('#addchapter').click(update);
});
function update(e) {
var range = $(this).getSelection();
$('#output').html(
"selected text:\n<span class=\"txt\">" + ((true) ? range.text.whitespace() : range.text) + "</span>\n\n"
);
}
String.prototype.whitespace = (function() {
if (!RegExp.escape) {
RegExp.escape = (function() {
var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' ];
var sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' );
return function(text) { return text.replace(sRE, '\\$1') }
})();
}
var ws = { "\r\n": "¶", "\n": "¶", "\r": "¶", "\t": "»", " ": "·" };
return ($.browser.msie) ? function() {
var s = this;
$.each(ws, function(i){ s = s.replace(new RegExp(RegExp.escape(i), 'g'), this) });
return s;
} : function () {
var s = this;
$.each(ws, function(i){ s = s.replace(new RegExp(RegExp.escape(i), 'g'), this + "\u200b") });
return s;
}
})();
//--><!]]>
</script>
</head>
<body>
<pre id="output"></pre>
<textarea id="area1" name="area1">textarea: foo bar baz</textarea>
<input type="button" value="add" id="addchapter">
</body>
</html>
I ended up using this - http://plugins.jquery.com/project/a-tools
The question seems clear enough but then the code is confusingly unrelated, so I'm going to answer the question as I understood it without the code.
The deleteSelectedText() function will delete the selected text from a <textarea> or <input type="text"> you provide and return you the text that was deleted.
function getSelectionBoundary(el, start) {
var property = start ? "selectionStart" : "selectionEnd";
var originalValue, textInputRange, precedingRange, pos, bookmark, isAtEnd;
if (typeof el[property] == "number") {
return el[property];
} else if (document.selection && document.selection.createRange) {
el.focus();
var range = document.selection.createRange();
if (range) {
// Collapse the selected range if the selection is not a caret
if (document.selection.type == "Text") {
range.collapse(!!start);
}
originalValue = el.value;
textInputRange = el.createTextRange();
precedingRange = el.createTextRange();
pos = 0;
bookmark = range.getBookmark();
textInputRange.moveToBookmark(bookmark);
if (/[\r\n]/.test(originalValue)) {
// Trickier case where input value contains line breaks
// Test whether the selection range is at the end of the
// text input by moving it on by one character and
// checking if it's still within the text input.
try {
range.move("character", 1);
isAtEnd = (range.parentElement() != el);
} catch (ex) {
log.warn("Error moving range", ex);
isAtEnd = true;
}
range.moveToBookmark(bookmark);
if (isAtEnd) {
pos = originalValue.length;
} else {
// Insert a character in the text input range and use
// that as a marker
textInputRange.text = " ";
precedingRange.setEndPoint("EndToStart", textInputRange);
pos = precedingRange.text.length - 1;
// Delete the inserted character
textInputRange.moveStart("character", -1);
textInputRange.text = "";
}
} else {
// Easier case where input value contains no line breaks
precedingRange.setEndPoint("EndToStart", textInputRange);
pos = precedingRange.text.length;
}
return pos;
}
}
return 0;
}
function deleteSelectedText(el) {
var start = getSelectionBoundary(el, true);
var end = getSelectionBoundary(el, false);
var val = el.value;
var selectedText = val.slice(start, end);
el.value = val.slice(0, start) + val.slice(end);
return selectedText;
}

Categories

Resources