Find and replace text without destroying click events - javascript

A client has asked for all of the trademark symbols (™ and ®) on their website to be styled in a specific way; given the quantity in which they appear—everywhere from titles to body text and navigation—we've decided to do this with JavaScript.
What we want to do is find every instance of ™ and ® in the page text (but not inside element attributes) and wrap them in <sup> tags so we can style them in CSS.
This is the code we currently have:
Trademark = {
init: function () {
$('body').contents().each(function () {
var element = $(this);
if (element.html()) {
element.html(element.html().replace(/(?![^<]+>)™/gi, '<sup class="trademark">™</sup>'));
element.html(element.html().replace(/(?![^<]+>)®/gi, '<sup class="trademark">®</sup>'));
}
});
}
}
$(function () {
Trademark.init();
})
It works well, but we're now suffering the problem that JavaScript click events aren't being registered on elements that have had their contents replaced—I'm assuming because they're being removed from the DOM when they're being manipulated.
Is there a modification to this (to the JS or regex) that will stop this from happening? Thanks!

Filter for textNodes only and replace the innerHTML of the parentNode, that way the elements themselves are never replaced and the event handlers should stay intact.
Trademark = {
init: function () {
$('*').contents().each(function() {
if (this.nodeType == 3 && this.nodeValue) {
if ( this.nodeValue.indexOf('™') != -1 || this.nodeValue.indexOf('®') != -1 ) {
this.parentNode.innerHTML = this.parentNode.innerHTML.replace(/(?![^<]+>)(™|®)/gi, '<sup class="trademark">$1</sup>');
}
}
});
}
}
FIDDLE

Related

How to reference all <input> and <textarea> and do an action if they have :focus?

I am trying to use plain Javascript to set up a function that fires when the S key is pressed AND the search overlay is not already open AND the S is not pressed when inside an <input> or <textarea>. The issue is in the third argument and I can't seem to figure it out.
Can you please tell me how to set up the third argument in the IF statement?
I have been trying to get an equivent of the JQuery is() function in regular JS. Since I don't know much about JS I am avoiding JQuery until I get the basics down. I have created a class for OOP, so the this. is referencing that.
My Javascript:
keyPressControl(event) {
if (event.keyCode == 83 && !this.isOverlayOpen && !document.querySelectorAll('input, textarea').hasFocus()) {
this.staffSearchOpen();
}
}
The this.staffSearchOpen(); should function when all three arguments noted above are true, but I can only get the first two to work properly.
The wording of the question is a little confusing but it looks like you're trying to exclude event that happen when an input field is in focus, not the other way around.
Instead of "hasFocus()" you could just build the rule into the selector itself as input:focus, textarea:focus:
document.addEventListener('keypress', function() {
if (document.querySelector('input:focus, textarea:focus')) {
console.log("keypress event was inside an input")
} else {
console.log("No input in focus");
}
})
<input>
<textarea></textarea>
...so your function could be:
keyPressControl(event) {
if (
event.keyCode == 83 &&
!this.isOverlayOpen &&
!document.querySelector('input:focus, textarea:focus')
) {
this.staffSearchOpen();
}
}
Do it the other way around:
var elems = document.querySelectorAll('input, textarea');
elems.foreach(function (elem) {
this.addEventListener("keydown",keyPressControl);
});
keyPressControl(event) {
//you won't get a key event here unless the element is the focus owner
if (event.keyCode == ...) {
this.staffSearchOpen();
}
}

CKEDITOR On element append

I can't seem to find an event in the documentation that will trigger when a specific element has been inserted into the HTML of a textarea.
For example if the user makes text bold, I would like to trigger an event when the 'b' tag is added into the HTML; as well as any other tag.
You can use change event and check if b is present in input:
var editor = CKEDITOR.inline(element, {
resize_enabled: false,
skin: 'rich-text,' + RX_RICH_TEXT.ckeditor.skinPath,
on: {
change: function () {
var dom = this.getData();
if (dom.includes('<b>')) {
// your logic
}
},

disable onclick event on div when clicking on inside <a> link (No js framework)

I have this piece of code:
<div id="mydiv" onclick="ajax_call()">
Mylink
</div>
I'd like ajax_call() to be called only when clicking on empty space inside div but not on "Mylink". Is it possible without any external javascript framework?
Moreover I have this piece of css:
div#mydiv:hover{
background-color: blue;
}
Is it possible to disable the :hover stylesheet when the cursor is placed over "Mylink" (in order to suggest that clicking on "Mylink" won't trigger ajax_call() but will take to myurl)?
Attach the function at child element with click event, After clicked on child element it's handler stops the immediate propagation, As a result ajax_call() will not be invoked.
HTML
<div id="mydiv" onclick="ajax_call()">
Mylink
</div>
javaScript
function ajax_call(){
alert("empt space");
}
//this function stops the propagation and not triggered above
//function when clicked on child element.
function notCall(event) {
event.stopImmediatePropagation();
return false;
}
DEMO
I'm not sure what you want but if I keep my imagination may be that this work, lucky !
$("div#mydiv a").hover(function(){
$(this).parent().css("background-color","transparent")
})
Sure, what you need is the event target || scrElement
Like
function ajax_call() {
var target = event.target ? event.target : event.srcElement;
if(target.id==="mydiv") { alert("good to go"); }
}
See : http://jsbin.com/qujuxufo/1/edit
Edit/Update ( missed the second part ) - Started to answer this before the q was closed - but might as well as it now ..
For the second part of the question - it is not possible to set parent elements in CSS ( it flows top to bottom ) - for that some more JS is needed.
See http://jsbin.com/cileqipi/1/edit
CSS
#mydiv:hover { background-color:green; color:#fff}
#mydiv.anchorhover, #mydiv.anchorhover:hover { background-color:white;}
Then JS
var _mydiv = document.getElementById("mydiv");
var _mydiv_anchors = _mydiv.getElementsByTagName("a");
function toggleClass() {
var addClass=true, cls="anchorhover";
if((_mydiv.className).indexOf(cls) >= 0){ addClass=false; }
if(addClass) {
_mydiv.className=_mydiv.className+=' '+cls;
} else {
/* remove */
_mydiv.className=_mydiv.className.replace(new RegExp('(\\s|^)'+cls+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, '');
}
}
for(var i=0, len=_mydiv_anchors.length; i<len; ++i) {
_mydiv_anchors[i].onmouseover = toggleClass;
_mydiv_anchors[i].onmouseout = toggleClass;
}
^ That feels like quite a trip compared to how simple jquery abstracts it .. >
$("#mydiv a").hover(function() {
$(this).parent().addClass("anchorhover");
}, function() {
$(this).parent().removeClass("anchorhover");
});
Either way, the principle is : to add a style class to the parent element on mouseover and remove it on mouseout

Converting Span to Input

I am developing web app, I have such a requirement that whenever user click on text inside span i need convert it into input field and on blur i need to convert it back to span again. So i am using following script in one of my jsp page.
Java Script:
<script type="text/javascript">
function covertSpan(id){
$('#'+id).click(function() {
var input = $("<input>", { val: $(this).text(),
type: "text" });
$(this).replaceWith(input);
input.select();
});
$('input').live('blur', function () {
var span=$("<span>", {text:$(this).val()});
$(this).replaceWith(span);
});
}
JSP Code:
<span id="loadNumId" onmouseover="javascript:covertSpan(this.id);">5566</span>
Now my problem is, everything works fine only for the first time. I mean whenever i click on the text inside span for the first time it converts into input field and again onblur it coverts back from input field to normal text. But if try once again to do so it won't work. Whats wrong with above script?
Would be good to change your dom structure to something like this (note that the span and the input are side by side and within a shared parent .inputSwitch
<div class="inputSwitch">
First Name: <span>John</span><input />
</div>
<div class="inputSwitch">
Last Name: <span>Doe</span><input />
</div>
Then we can do our JS like this, it will support selecting all on focus and tabbing to get to the next/previous span/input: http://jsfiddle.net/x33gz6z9/
var $inputSwitches = $(".inputSwitch"),
$inputs = $inputSwitches.find("input"),
$spans = $inputSwitches.find("span");
$spans.on("click", function() {
var $this = $(this);
$this.hide().siblings("input").show().focus().select();
}).each( function() {
var $this = $(this);
$this.text($this.siblings("input").val());
});
$inputs.on("blur", function() {
var $this = $(this);
$this.hide().siblings("span").text($this.val()).show();
}).on('keydown', function(e) {
if (e.which == 9) {
e.preventDefault();
if (e.shiftKey) {
$(this).blur().parent().prevAll($inputSwitches).first().find($spans).click();
} else {
$(this).blur().parent().nextAll($inputSwitches).first().find($spans).click();
}
}
}).hide();
I understand you think that element replacement is a nice thing, however, I would use a prompt to get the text. Why? It is a lot easier and actually a bit prettier for the user as well. If you are curious on how to do it, I show you.
html:
<span class='editable'>foobar</span>
js:
$(function()
{
$('span.editable').click(function()
{
var span = $(this);
var text = span.text();
var new_text = prompt("Change value", text);
if (new_text != null)
span.text(new_text);
});
});
http://jsfiddle.net/qJxhV/1/
First, you need to change your click handler to use live() as well. You should take note, though, that live() has been deprecated for quite a while now. You should be using on() in both cases instead.
Secondly, when you replace the input with the span, you don't give the element an id. Therefore, the element no longer matches the selector for your click handler.
Personally, I would take a different (and simpler) approach completely. I would have both the span and in the input in my markup side by side. One would be hidden while the other is shown. This would give you less chance to make mistakes when trying to recreate DOM elements and improve performance since you won't constantly be adding/removing elements from the DOM.
A more generic version of smerny's excellent answer with id's can be made by slightly altering two lines:
$input.attr("ID", "loadNum"); becomes $input.attr("ID", $(this).attr("ID")); -- this way, it simply takes the current id, and keeps it, whatever it is.
Similarly,
$span.attr("ID", "loadNum"); becomes $span.attr("ID", $(this).attr("ID"));
This simply allows the functions to be applied to any div. With two similar lines added, both id and class work fine. See example.
I have done little change in code, By using this input type cant be blank, it will back to its real value.
var switchToInput = function () {
var $input = $("<input>", {
val: $(this).text(),
type: "text",
rel : jQuery(this).text(),
});
$input.addClass("loadNum");
$(this).replaceWith($input);
$input.on("blur", switchToSpan);
$input.select();
};
var switchToSpan = function () {
if(jQuery(this).val()){
var $text = jQuery(this).val();
} else {
var $text = jQuery(this).attr('rel');
}
var $span = $("<span>", {
text: $text,
});
$span.addClass("loadNum");
$(this).replaceWith($span);
$span.on("click", switchToInput);
}
$(".loadNum").on("click", switchToInput);
jsFiddle:- https://jsfiddle.net/svsp3wqL/

Is there an easier way to reference the source element for an event?

I'm new to the whole JavaScript and jQuery coding but I'm currently doing this is my HTML:
<a id="tog_table0"
href="javascript:toggle_table('#tog_table0', '#hideable_table0');">show</a>
And then I have some slightly ponderous code to tweak the element:
function toggle_table(button_id, table_id) {
// Find the elements we need
var table = $(table_id);
var button = $(button_id);
// Toggle the table
table.slideToggle("slow", function () {
if ($(this).is(":hidden"))
{
button.text("show");
} else {
button.text("hide");
}
});
}
I'm mainly wondering if there is a neater way to reference the source element rather than having to pass two IDs down to my function?
Use 'this' inside the event. Typically in jQuery this refers to the element that invoked the handler.
Also try and avoid inline script event handlers in tags. it is better to hook those events up in document ready.
NB The code below assumes the element invoking the handler (the link) is inside the table so it can traverse to it using closest. This may not be the case and you may need to use one of the other traversing options depending on your markup.
$(function(){
$('#tog_table0').click( toggle_table )
});
function toggle_table() {
//this refers to the element clicked
var $el = $(this);
// get the table - assuming the element is inside the table
var $table = $el.closest('table');
// Toggle the table
$table.slideToggle("slow", function () {
$el.is(":hidden") ? $el.text("show") : $el.text("hide");
}
}
You can do this:
show
and change your javascript to this:
$('a.tableHider').click(function() {
var table = $(this.name); // this refers to the link which was clicked
var button = $(this);
table.slideToggle("slow", function() {
if ($(this).is(':hidden')) { // this refers to the element being animated
button.html('show');
}
else {
button.html('hide');
}
});
return false;
});
edit: changed script to use the name attribute and added a return false to the click handler.
I'm sure this doesn't answer your question, but there's a nifty plugin for expanding table rows, might be useful to check it out:
http://www.jankoatwarpspeed.com/post/2009/07/20/Expand-table-rows-with-jQuery-jExpand-plugin.aspx

Categories

Resources