I am using the following code to check for an empty paragraph but it never returns true. Why?
var isEmpty = pageChildren[i].outerHTML.toUpperCase() === "<P></P>";
var isSpace = pageChildren[i].outerHTML.toUpperCase() === "<P> </P>";
var isNbsp = pageChildren[i].outerHTML.toUpperCase() === "<P>&NBSP;</P>";
if(!isEmpty && !isSpace && !isNbsp){
//do something
}else{
//do something else
}
This is a copy paste of what IE8 debug tools tells me is in the outerHTML: "<P></P>"
This is being read from an iFrame so i need to remove the <p></p> tags also as the function above this triggers off of the number of children elements in the body of the frame.
Additionally this runs in an HTA application on IE only. The userbase/configuration is highly controlled.
If you're sure your object is a paragraph, you should use innerHTML instead :
var isEmpty = pageChildren[i].innerHTML.trim().length==0;
(note that the MDN proposes a replacement for IE8 which lacks trim).
You could also use textContent but the downside is that you have to do a different test for IE.
Since trim() isn't supported by all browsers, I suggest removing all spaces with RegEx:
var isEmpty =
(pageChildren[i].innerHTML.replace(/\s/g, '').length == 0) ? true : false;
It seems that IE < 9 prepends a carriage return, line feed to the outerHTML result, so for example pageChildren[i].outerHTML.toUpperCase() === '\r\n<P></P>' returns true for an empty p element.
Related
I've been working on creating an HTML parser and formatter, and I've just added a feature to optionally render whitespace visible, by replacing spaces with · (middle dot) characters, adding arrows for tabs and newlines, etc.
The full in-progress source code is here: https://github.com/kshetline/html-parser, and the most relevant file that's doing the CSS styling of HTML is here: https://github.com/kshetline/html-parser/blob/master/src/stylizer.ts.
While it's nice to be able to visualize whitespace when you want to, you wouldn't want spaces to be turned into middle dot characters if you select and copy the text. But that's just what happens, at least without some intervention.
I have found a crude way to fix the problem with a bit of JavaScript, which I've put into a Code Pen here: https://codepen.io/kshetline/pen/NWKYZJg.
document.body.addEventListener('copy', (event) => {
let selection = document.getSelection().toString();
selection = selection.replace(/·|↵\n|↵/g, ch => ch === '·' ? ' ' : '\n');
event.clipboardData.setData('text/plain', selection);
event.preventDefault();
});
I'm wondering, however, if there's a better way to do this.
My first choice would be something that didn't rely on JavaScript at all, like if there's some way via CSS or perhaps some accessibility-related HTML attribute that would essentially say, "this is the real text that should be copied, not what you see on the screen".
My second choice would be if someone can point me to more detailed documentation of the JavaScript clipboard feature than I've been able to find, because if I have to rely on JavaScript, I'd at least like my JavaScript to be smarter. The quick-and-dirty solution turns every middle dot character into a space, even if it was truly supposed to be a middle dot in the first place.
Is there enough info in the clipboard object to figure out which of the selected text has what CSS styling, so I could know to convert only the text that's inside <span>s which have my whitespace class, and still also find the rest of the non-whitespace text, in proper order, to piece it all back together again?
I still couldn't find much documentation on how selection objects work, but I played around with them in the web console, and eventually figured out enough to get by.
This is the JavaScript I came up with:
function restoreWhitespaceStrict(s) {
return s.replace(/·|[\u2400-\u241F]|\S/g, ch => ch === '·' ? ' ' :
ch.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ch.charCodeAt(0) - 0x2400) : '');
}
const wsReplacements = {
'·': ' ',
'→\t': '\t',
'↵\n': '\n',
'␍\r': '\r',
'␍↵\r\n': '\r\n'
}
function restoreWhitespace(s) {
return s.replace(/·|→\t|↵\n|␍\r|␍↵\r\n|→|↵|␍|[\u2400-\u241F]/g, ws =>
wsReplacements[ws] || (ws.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ws.charCodeAt(0) - 0x2400) : ''));
}
document.body.addEventListener('copy', (event) => {
const selection = document.getSelection();
let newSelection;
let copied = false;
if (selection.anchorNode && selection.getRangeAt) {
try {
const nodes = selection.getRangeAt(0).cloneContents().childNodes;
let parts = [];
// nodes isn't a "real" array - no forEach!
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node.classList && node.classList.contains('whitespace'))
parts.push(restoreWhitespaceStrict(node.innerText));
else if (node.localName === 'span')
parts.push(node.innerText);
else
parts.push(node.nodeValue);
}
newSelection = parts.join('');
copied = true;
}
catch (err) {}
}
if (!copied)
newSelection = restoreWhitespace(selection.toString());
event.clipboardData.setData('text/plain', newSelection);
event.preventDefault();
});
I've tried this on three browsers (Chrome, Firefox, and Safari), and it's working on all of them, but I still took the precaution of both testing for the presence of some of the expected object properties, and then also using try/catch, just in case I hit an incompatible browser, in which case the not-so-smart version of fixing the clipboard takes over.
It looks like the selection is handled as a list of regular DOM nodes. Chrome's selection object has both an anchorNode and an extentNode to mark the start and end of the selection, but Firefox only has the anchorNode (I didn't check Safari for extentNode). I couldn't find any way to get the full list of nodes directly, however. I could only get the full list using the cloneContents() method. The first and last nodes obtained this way are altered from the original start and end nodes by being limited to the portion of the text content that was selected in each node.
In the below code, the else and not the if is always executed even though the alert tells me that state does in fact contain the string payment.
var state = $('.check-state').text();
alert(state); // payment
if (state === "payment")
alert('hello');
else
alert('not match')
Why is that?
I am guessing your HTML look sort of like this:
<div class="check-state">
payment
</div>
Then the .text() will return everything between the > and the <, including the whitespace. So what you get is "\n payment\n", not "payment".
The solution is to trim the whitespace away, using jQuerys $.trim():
var state = $.trim($('.check-state').text());
On a side note, I would recommend you to use console.log() instead of alert() for debugging. In most browsers, that would have allowed you to detect where the error was since you would have clearly seen the whitespace.
Can you tell me what I am missing in writing this code?
<button onclick="getBrowserName()">You Browser Name?</button>
<script>
function getBrowserName()
{
//Uses external interface to reach out to browser and grab browser useragent info.
var browserAgent:String = ExternalInterface.call("function getBrowser(){return navigator.userAgent;}");
//Determines brand of browser using a find index. If not found indexOf returns (-1).
if(browserAgent != null && browserAgent.indexOf("Firefox")>= 0)
{
alert("Firefox");
}
else if(browserAgent != null && browserAgent.indexOf("Safari")>= 0)
{
alert("Safari");
}
else if(browserAgent != null && browserAgent.indexOf("MSIE")>= 0)
{
alert("IE");
}
else if(browserAgent != null && browserAgent.indexOf("Opera")>= 0)
{
alert("Opera");
}
else
{
alert("Undefined");
}
return 0;
}
</script>
Well, there are a few things wrong here.
var browserAgent: String: it appears that you're using actionscript syntax, but JS uses dynamic typing, so var is all you need. There's no need to explicitly define the variable's data type, and if you try to do it this way in JS, it's going to give you syntax errors.
ExternalInterface.call: this is another carryover from ActionScript: you don't need this. In fact, it won't work at all because there's no ExternalInterface class in standard JS.
Your getBrowser() function is unnecessary. You're setting browserAgent equal to the result of calling a function from an ExternalInterface, but you can do this directly: var browserAgent = window.navigator.userAgent.
When I fixed those things, it worked fine.
Next time, I would recommend checking the browser console, because, if nothing is happening, the errors that appear there will help you solve your issue nine times out of ten.
Demo
If you replace this line
var browserAgent:String = ExternalInterface.call("function getBrowser(){return navigator.userAgent;}");
with this line:
var browserAgent = window.navigator.userAgent;
Then your script works fine on my side.
However, the criteria you use to test the engine are not precise. Have a look at this:
http://www.useragentstring.com/pages/useragentstring.php
There are many browsers that will tell you Firefox even if they another brand. But they are based on each other or they use a specific engine that is built in other browsers too.
If I use your script with a Chrome browser, it says "Safari" instead of "undefined".
About the punctuation: I know of only two places in Javascript where to use the double point:
the conditional operator a = b ? c : d;
the attribute - value assignment in object notation: { name : value }
Your code line containing :String = ExternalInterface... reminds me rather on ActionScript (?).
Im not quite sure what the follow code should be doing. Are you sure its correct?
var browserAgent:String =
ExternalInterface.call("function getBrowser(){return navigator.userAgent;}");
I would expect this code to simply look like this:
var browserAgent = navigator.userAgent;
Below is a example with this change.
http://jsbin.com/lukasere/1/edit
If i had a string:
hey user, what are you doing?
How, with regex could I say: look for user, but not inside of < or > characters? So the match would grab the user between the <a></a> but not the one inside of the href
I'd like this to work for any tag, so it wont matter what tags.
== Update ==
Why i can't use .text() or innerText is because this is being used to highlight results much like the native cmd/ctrl+f functionality in browsers and I dont want to lose formatting. For example, if i search for strong here:
Some <strong>strong</strong> text.
If i use .text() itll return "Some strong text" and then I'll wrap strong with a <span> which has a class for styling, but now when I go back and try to insert this into the DOM it'll be missing the <strong> tags.
If you plan to replace the HTML using html() again then you will loose all event handlers that might be bound to inner elements and their data (as I said in my comment).
Whenever you set the content of an element as HTML string, you are creating new elements.
It might be better to recursively apply this function to every text node only. Something like:
$.fn.highlight = function(word) {
var pattern = new RegExp(word, 'g'),
repl = '<span class="high">' + word + '</span>';
this.each(function() {
$(this).contents().each(function() {
if(this.nodeType === 3 && pattern.test(this.nodeValue)) {
$(this).replaceWith(this.nodeValue.replace(pattern, repl));
}
else if(!$(this).hasClass('high')) {
$(this).highlight(word);
}
});
});
return this;
};
DEMO
It could very well be that this is not very efficient though.
To emulate Ctrl-F (which I assume is what you're doing), you can use window.find for Firefox, Chrome, and Safari and TextRange.findText for IE.
You should use a feature detect to choose which method you use:
function highlightText(str) {
if (window.find)
window.find(str);
else if (window.TextRange && window.TextRange.prototype.findText) {
var bodyRange = document.body.createTextRange();
bodyRange.findText(str);
bodyRange.select();
}
}
Then, after you the text is selected, you can style the selection with CSS using the ::selection selector.
Edit: To search within a certain DOM object, you could use a roundabout method: use window.find and see whether the selection is in a certain element. (Perhaps say s = window.getSelection().anchorNode and compare s.parentNode == obj, s.parentNode.parentNode == obj, etc.). If it's not in the correct element, repeat the process. IE is a lot easier: instead of document.body.createTextRange(), you can use obj.createTextRange().
$("body > *").each(function (index, element) {
var parts = $(element).text().split("needle");
if (parts.length > 1)
$(element).html(parts.join('<span class="highlight">needle</span>'));
});
jsbin demo
at this point it's evolving to be more and more like Felix's, so I think he's got the winner
original:
If you're doing this in javascript, you already have a handy parsed version of the web page in the DOM.
// gives "user"
alert(document.getElementById('user').innerHTML);
or with jQuery you can do lots of nice shortcuts:
alert($('#user').html()); // same as above
$("a").each(function (index, element) {
alert(element.innerHTML); // shows label text of every link in page
});
I like regexes, but because tags can be nested, you will have to use a parser. I recommend http://simplehtmldom.sourceforge.net/ it is really powerful and easy to use. If you have wellformed xhtml you can also use SimpleXML from php.
edit: Didn't see the javascript tag.
Try this:
/[(<.+>)(^<)]*user[(^>)(<.*>)]/
It means:
Before the keyword, you can have as many <...> or non-<.
Samewise after it.
EDIT:
The correct one would be:
/((<.+>)|(^<))*user((^>)|(<.*>))*/
Here is what works, I tried it on your JS Bin:
var s = 'hey user, what are you doing?';
s = s.replace(/(<[^>]*)user([^<]>)/g,'$1NEVER_WRITE_THAT_ANYWHERE_ELSE$2');
s = s.replace(/user/g,'Mr Smith');
s = s.replace(/NEVER_WRITE_THAT_ANYWHERE_ELSE/g,'user');
document.body.innerHTML = s;
It may be a tiny little bit complicated, but it works!
Explanation:
You replace "user" that is in the tag (which is easy to find) with a random string of your choice that you must never use again... ever. A good use would be to replace it with its hashcode (md5, sha-1, ...)
Replace every remaining occurence of "user" with the text you want.
Replace back your unique string with "user".
this code will strip all tags from sting
var s = 'hey user, what are you doing?';
s = s.replace(/<[^<>]+>/g,'');
I'm really new at coding, sorry if any of this sounds silly or stupid. We have a new project to come up with a new webpage. I have a multiple condition if statement and would like to call a xsl template if condition is met. Here's how I have it now and it doesn't work at all.
<script>
function getSelectedValue()
{
if("document.getElementById('type').value==1 and document.getElementById('cablegroup5').value==9"+
"document.getElementById('cablegroup3').value==22 and document.getElementById('cablelength').value==11")
{
<xsl:call-template name="PN">
<xsl:with-param name="Cable">ABC111-06</xsl:with-param>
</xsl:call-template>
}
}
</script>
I know the first part works, I've tested it with an alert message and that works just fine. These are all activated by a button(onclick) next to multiple drop down menus. Is there a way to get this to work? Any help would be really appreciated. Thanks.
You're confused about the processing model. Is the script element generated by XSLT? If so, the call-template will probably be called at the time the script is generated. It won't be called at the time the script is executed. Javascript code isn't going to magically execute XSLT instructions.
There are a couple of issue within the script that would prevent the if statement executing correctly.
The boolean and operator in JavaScript is && not and. Note the if you use & this would be a bitwise and.
The tests should not be a string. Due to JavaScript type coersion it a string will be converted to a boolean. A null or empty string '' will evaluate false, all other strings will evaluate true. Currently you have
if("test1 and test2")
This should be
if(test1 && test2)
So far your updated script would be
<script>
function getSelectedValue()
{
if(document.getElementById('type').value==1 &&
document.getElementById('cablegroup5').value==9 &&
document.getElementById('cablegroup3').value==22 &&
document.getElementById('cablelength').value == 11)
{
// Process Xml
}
}
</script>
You need to use the browser xml parser to handle your xml. I will assume that you have an xml string, if you have a document object then you will have to change the following slightly, this is from w3schools.
var xmlString = "<Products>" +
"<Product partnumber='foo'>This is product 1</Product>" +
"<Product partnumber='bar'>This is product 2</Product>" +
"</Products>";
// Load into an XML document
var myDoc;
if (window.DOMParser)
{
var parser=new DOMParser();
myDoc=parser.parseFromString(xmlString,"text/xml");
}
else // Internet Explorer
{
myDoc=new ActiveXObject("Microsoft.XMLDOM");
myDoc.async="false";
myDoc.loadXML(xmlString);
}
// Get all product nodes
var products = myDoc.getElementsByTagName('Product');
var i, targetProduct, partNumber;
for(i = 0; i < products.length; i += 1){
// Get the partnumber attribute
partnumber = products[i].attributes.getNamedItem('partnumber');
// Ensure that the partnumber exists and its value is what is wanted
if(partnumber && partnumber.value == 'foo'){
targetProduct = products[i];
// Exit for
break;
}
}
// If the product has been found alert its value.
if(targetProduct != null){
alert(targetProduct.textContent || targetProduct.text);
}
If you were selected a node by id then you could use xmlDoc.getElementById instead of iterating through all nodes of a type and checking attributes.
To select the text value of an xml node most browsers use the property textContent although Internet Explorer uses text. The line
targetProduct.textContent || targetProduct.text
returns textContent if it is present and not null, or the value of text.