Live edit text in an SVG that's embedded in a website - javascript

I'm doing some find and replace in embedded SVGs. Part of a web to print service I'm creating.
I have text tags inside the SVG like {name}, {title}, {phone}, etc.
I wrote a script to replace these values and it live updates the embedded SVG. It currently works alright.
It's using the jQuery SVG plugin to load the SVG.
// Callback after loading external document
function loadDone(svg, error) {
svg.configure({viewBox: '0 0 315 180', width: 579, height: 331}, true); //resize the svg. viewBox must be the same size as the initial width defined in the SVG
var textElems = document.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'tspan');
var doc = document.getElementById('svgload').contentDocument;
for (var i = 0; i < textElems.length; i++) {
var id = textElems[i].childNodes[0].nodeValue;
id = id.replace("{",""); //remove brackets {}
id = id.replace("}","");
alert(id);
$("#formContainer").append('<p>' + capitalize(id) + ': <input type="text" id="' + id + '" class="replace" />\n');
$("#formContainer").append('<input type="hidden" id="' + id + 'PrevVal" value="{' + id + '}" /></p>');
}
$('.replace').keyup(function() {
var prevValID = $(this).attr("id") + "PrevVal"; //determine the hidden input id
var oldVal = $("#"+prevValID).val(); //set oldVal to the hidden input value, first time it's {id}
var currentVal = $(this).val(); //set the currentVal to the user inputted value
changeText(oldVal,currentVal); //swap the oldVal with the currentVal
$('#'+prevValID).attr("value",currentVal); //set the hidden input value to the last inputted user value
svg.configure({viewBox: '0 0 315 180', width: 579, height: 331}, true); //"resize" to svg to clear the text. some browsers break the new text until the svg is "zoomed", or in this case, resized
});
function changeText(oldVal,newVal) { //swap the values inside the SVG file
for (var i = 0; i < textElems.length; i++) {
if (textElems[i].childNodes[0].nodeValue == oldVal) {
textElems[i].childNodes[0].nodeValue = newVal;
}
}
}
}
function capitalize(str) { //capitalize first letter of words
var firstLetter = str.slice(0,1);
return firstLetter.toUpperCase() + str.substring(1);
}
There are some bugs though. For example, since I'm creating hidden divs to store the previous value of the SVG text one can create a situation where typing the same thing into two text boxes creates two identical IDs and then further typing updates both text elements in the embedded SVG. It also doesn't like tags that have spaces in them, like {full name} versus {name}.
Any suggestions on how to clean this whole thing up? I know I should be able to detect tags (searching for {}) and then get the text or tspan id associated with them and update the node value that way, however, I've got severe coding block and can't quite start on it!

Managed to trim it down to this:
// Callback after loading external document
function loadDone(svg, error) {
svg.configure({viewBox: '0 0 315 180', width: 579, height: 331}, true); //resize the svg. viewBox must be the same size as the initial width defined in the SVG
var textElems = document.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'tspan');
var doc = document.getElementById('svgload').contentDocument;
for (var i = 0; i < textElems.length; i++) {
var textID = textElems[i].getAttribute('id');
var textValue = textElems[i].childNodes[0].nodeValue;
textValue = textValue.replace("{",""); //remove brackets {}
textValue = textValue.replace("}","");
$("#formContainer").append('<p style="text-transform:capitalize;">' + textValue + ': <input type="text" id="' + textID + '" class="replace" />\n');
}
$('.replace').keyup(function() {
var textToChange = document.getElementById($(this).attr('id'));
textToChange.childNodes[0].nodeValue = $(this).val();
svg.configure({viewBox: '0 0 315 180', width: 579, height: 331}, true); //"resize" to svg to clear the text. some browsers break the new text until the svg is "zoomed", or in this case, resized
});
}
And it's doing exactly what I want.
Hopefully that helps anyone else looking to do text replacements in embedded SVG's :)

Related

how to use jquery to insert a character at specified position into monospaced textarea

I have a monospaced textarea (not unlike the stackexchange editor). When my user clicks, I need a character to automagically appear on the previous line using jQuery. I know I need to use .click() to bind a function to that event, but the logic of the function eludes me.
Desired Behavior...user will click at position of the asterisk *
Here is some text in my editor.
When I double click at a position*
I want to insert a new line above, with a new character at the same position
The above text should become the following after the function gets run
Here is some text in my editor.
*
When I double click at a position*
I want to insert a new blank line above, at the same position
What I have tried:
I have found the caret jQuery plugin, which has a function called caret() that I can get to find the position of the the asterisk when I click (the position is 74).
<script src='jquery.caret.js'></script>
$('textarea').click(function(e) {
if (e.altKey){
alert($("textarea").caret());
}
});
But I really need to know the position of the character within the line, not the entire textarea. So far this eludes me.
Here's something without using caret.js
$('textarea').dblclick(function(e){
var text = this.value;
var newLinePos = text.lastIndexOf('\n', this.selectionStart);
var lineLength = this.selectionStart - newLinePos;
var newString = '\n';
for(var i=1; i < lineLength; ++i){
newString += ' ';
}
newString += text.substr(this.selectionStart,this.selectionEnd-this.selectionStart);
this.value = [text.slice(0, newLinePos), newString, text.slice(newLinePos)].join('');
});
Here's a fiddle. Credit to this post for 'inserting string into a string at specified position'.
Just realised that doing that on the top line is a bit broken, I'll have a look when I get home!
Update
Fixed the top-line problem.
if(newLinePos == -1){
this.value = newString + '\n' + this.value;
} else {
this.value = [text.slice(0, newLinePos), '\n'+newString, text.slice(newLinePos)].join('');
}
http://jsfiddle.net/daveSalomon/3dr8k539/4/
Assuming you know the position of the caret in the whole text area here's something you might do with it.
function getCaretPosition(text, totalOffset) {
var line = 0, pos = 0;
for (var i = 0; i < Math.min(totalOffset, text.length); i++) {
if (text[i] === '\n') {
line++;
pos = 0;
} else {
pos++;
}
}
return { row: line, col: pos };
}
var caretPosition = getCaretPosititon($("textarea").val(), $("textarea").caret());

Removing elements from a document in Javascript [duplicate]

This question already has answers here:
JavaScript DOM remove element
(4 answers)
Closed 8 years ago.
Working on building this to-do list app to learn JS better.
I am able to insert text into box and have it add a new div element to the page with the relevant text residing within the div. The code that adds the div to the page automatically applies the class .newItem then adds an incrementing id i0, i1, i2, etc each time it's clicked. Everything works without an issue. Now, I have been fiddling around with the code to be able to click a single div element and have it remove itself from the page, but can't seem to get it to work.
var iDN = 0;
//Function that adds a new div with a custom incrementing id number
document.getElementById('add_button').onclick = function () {
var taskName = document.getElementById('add_task').value; // Store the value in the textbox into a variable
document.querySelector('.shit_to_do').innerHTML += '<div class = "newItem" id = "i' + iDN + '"' + 'onclick = removeEl()' + '>' + taskName + '</div>';
iDN += 1;
};
document.getElementById('testing').onclick = function () {
var parentNode = document.querySelector('.shit_to_do');
parentNode.removeChild(parentNode.children[0]);
}
function removeEl() {
for (i = 0; i < iDN; i++) {
if (document.getElementById('i' + i).onclick) {
document.getElementById('i' + i).display.style = 'none';
}
alert(i);
}
}
The for loop was really some random option I was trying to figure out how things were working onclick for each div, but didn't get to far with that one.
tl;dr:
I want to add click events to the new divs added onto the page in a single, universal function.
The value of document.getElementById('i' + i).onclick will be null if you've not set a handler to this attribute/property, otherwise it will be a function. null is always falsy, a function is always truthy.
To remove your element, you'll either have to look at this or e.target where e is the click event, and then call the DOM method node.removeChild(child).
The "quick and dirty" solution is to pass this into removeEl and remove it that way,
// ...
document.querySelector('.shit_to_do').innerHTML += '<div class="newItem" id="i' + iDN + '" onclick="removeEl(this)">' + taskName + '</div>';
// ...
function removeEl(elm) {
elm.parentNode.removeChild(elm);
}
I also removed the strange spacing between attribute names and values in your HTML
A perhaps "cleaner" solution is to create your nodes and attach listeners all by using DOM methods
function createDiv(index, taskname) {
var d = document.createElement('div');
d.setAttribute('id', 'i' + index);
d.textContent = taskname;
return d;
}
function removeElm() {
this.parentNode.removeChild(this);
}
var iDN = 0;
document.getElementById('add_button').addEventListener('click', function () {
var taskName = document.getElementById('add_task').value,
list = querySelector('.shit_to_do'),
div = createDiv(iDN, taskName);
div.addEventListener('click', removeElm);
list.appendChild(div);
iDN += 1;
});
This way means the browser does not re-parse any HTML as it not use element.innerHTML, which is a dangerous property may destroy references etc (along with usually being a bit slower)
Helpful links
node.addEventListener
document.createElement
node.appendChild

How to replace font tags with span?

I'm trying to replace all instances of a font
tag that has a color attribute like this:
<font color="red">Some text</font>
with this:
<span style="color: red;">Some text</span>
I did some hunting on StackOverflow and found this link, and modeled my code after it:
Javascript JQuery replace tags
I've created a little jQuery loop below that is supposed to do the following:
Go through the string(contents of a div)
Get the font color attribute
Replace the tags with a
Set a CSS style attribute with the appropriate color.
Right now, it doesn't work. I just get an error stating that 'replaceWith' is not a function.
$('font').each(function () {
var color;
this.$('font').replaceWith(function () {
color = this.attr("color");
return $('<span>').append($(this).contents());
});
this.$("span").attr("style", "color=" + color);
});
Any help would be greatly appreciated!
There is no need for each, replaceWith will do it internally.
$("font").replaceWith( //find all font tags and call replace with to change the element
function(){
var tag = $(this);
return $("<span/>") //create new span
.html(tag.html()) //set html of the new span with what was in font tag
.css("color", tag.attr("color")); //set the css color with the attribute
}
);
JSFiddle: http://jsfiddle.net/qggadmmn/
$('font').each(function () {
var $this = $(this);
var color = $this.attr('color');
var text = $this.text();
var span = $('<span style="' + color '">' + text + '</span>';
$this.before(span);
$this.remove();
});
Quite a late answer, but I think it's still worth it posting.
If your font tags may have other attributes than just color (ie: face, size), this would cover them all:
HTML (Example)
<font color="red" size="3">Some text</font>
<br />
<font color="blue" face="Verdana">Some text</font>
jQuery (Javascript):
$(function () {
var fontSizes = [
'xx-small', // 1
'x-small', // 2
'small', // 3
'medium', // 4
'large', // 5
'x-large', // 6
'xx-large' // 7
];
$('font').replaceWith(function () {
var attrs = this.attributes;
var span = $('<span />').append($(this).contents());
for (var i = 0; i < attrs.length; i++) {
var name = attrs[i].name;
var value = attrs[i].value;
if (name.indexOf('face') >= 0) {
name = 'font-family';
}
if (name.indexOf('size') >= 0) {
name = 'font-size';
value = fontSizes[value - 1];
}
span.css(name, value);
}
return span;
});
});
Demo

Create multiple selection at the same time when using document.selection in Javascript

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]);
}
}

Populating div with checkboxes

I use this code to determine checkbox width and height:
var chk = $('input[type="checkbox"]:first');
chkWidth = chk.innerWidth()
+ parseInt(chk.css('margin-left').replace('px', ''))
+ parseInt(chk.css('margin-right').replace('px', '')); /*IS there better way instead of px replace?? */
chkHeight = chk.innerHeight()
+ parseInt(chk.css('margin-top').replace('px', ''))
+ parseInt(chk.css('margin-bottom').replace('px', ''));
fieldWidth = gamefield.innerWidth();
fieldHeight = gamefield.innerHeight();
Then i try to fill div with the number of checkboxes that exactly fits given div. In fact i get a lot of checkboxes out of div.
http://dl.dropbox.com/u/3473965/jq/tetris.html
http://dl.dropbox.com/u/3473965/jq/tetris.js
Is there anyway to solve this? Thank you!
leaving other things as such, this makes the difference (on FF3.5):
var cols=Math.floor(fieldWidth/chkWidth);
var rows=Math.floor(fieldHeight/chkHeight);
var html='';
for(var i=0;i<=rows;i++) //if yo use i<rows there is space for one more row
{
for(var j=0;j<cols;j++)
{
html +="<input type='checkbox'/>";
}
}
gamefield.html(html);

Categories

Resources