Regex works only once - javascript

I've recently been learning JavaScript by creating a little to do list web app here.
So far almost everything's working, but I have an issue if you try to check and uncheck an item more than once. If you keep checking/unchecking you'll see the delete button disappear and --> appear after the urgency icon.
The change of icon is done by a Regex changing code from commented to un-commented. I just don't understand why if it works once, it doesn't work every time?
if (tr.outerHTML.indexOf("checked=\"\"") >= 0) {
// replace checked with unchecked
var cookieHTML = tr.outerHTML.replace(/checked=\"\" class=\"list-checkbox\"/, 'class=\"list-checkbox\"')
.replace(/<tr class=\"list-row done\"/, '<tr class=\"list-row\"')
// change delete button to urgency.
.replace(/<!--<span aria-hidden=\"true\" data-icon=\"c\"/, '<span aria-hidden="true" data-icon="c"')
.replace(/alt=\"Neutral\"><\/span>-->/, 'alt="Neutral"></span>')
.replace(/<!--<span aria-hidden=\"true\" data-icon=\"f\"/, '<span aria-hidden="true" data-icon="f"')
.replace(/alt=\"Urgent\"><\/span>-->/, 'alt="Urgent"></span>')
.replace(/<span aria-hidden=\"true\" data-icon=\"e\"/, '<!--<span aria-hidden="true" data-icon="e"')
.replace(/onclick=\"deletetodo\(this\)\"><\/span>/, 'onclick="deletetodo(this)"></span>-->');
} else {
// else add checked to the input.
var cookieHTML = tr.outerHTML.replace(/class=\"list-checkbox\"/, 'checked class=\"list-checkbox\"')
.replace(/<tr class=\"list-row\"/, '<tr class=\"list-row done\"')
// change urgency to delete button.
.replace(/<span aria-hidden=\"true\" data-icon=\"c\"/, '<!--<span aria-hidden="true" data-icon="c"')
.replace(/alt=\"Neutral\"><\/span>/, 'alt="Neutral"></span>-->')
.replace(/<span aria-hidden=\"true\" data-icon=\"f\"/, '<!--<span aria-hidden="true" data-icon="f"')
.replace(/alt=\"Urgent\"><\/span>/, 'alt="Urgent"></span>-->')
.replace(/<!--<span aria-hidden='true' data-icon='e'/, '<span aria-hidden="true" data-icon="e"')
.replace(/onclick='deletetodo\(this\)'><\/span>-->/, 'onclick="deletetodo(this)"></span>');
}
This is the (rather large!) chunk of JS that controls this. Any ideas what's wrong? Or maybe a better way of changing these icons around?
Thanks!

I would say: you're are doing it wrong. Using a string / regex replacement method is not the right way to go imho.
Instead of doing those replacement use DOM methods, i.e.:
someElement.setAttribute('data-icon', 'f');
someElement.setAttribute('alt', 'Urgent');
A simple example can be found here: http://jsbin.com/iwakof/1/edit
I know this isn't a direct answer to your question, but trust me this is the way to go

That's awesome that you are learning JavaScript. Nice job. But, I'm quite glad that you posted this question as it looks like you could use a couple of pointers.
The answer to your question is - yes there is a much simpler way to achive this effect - which I will get to shortly. But first - I notice that at the bottom of your todo app you include a library called JQuery
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
This library will be of huge help to you, not only in the function you describe above, but to the majority of the code you have written. You will end up with much cleaner and self explanatory code.
http://jquery.com/
Basically what JQuery allows you to do, is to manipulate the state of the DOM. You definatly want to begin here.
Here is small sample which shows a check box who can be checked or unchecked, and on change, have an element shown' or hidden as desired.
http://jsfiddle.net/m4vGE/5/
Please - do take the time to have a look into JQuery - its a great first step you can take to increase your produtivity and reduce complexity in your JavaScript
Also - as a side note, if you find yourself using js to build HTML with strings, the answer is invariably "there is a better way"

If all you are trying to do is change icons based on a checkbox being checked or no, you could do something like this.
function getVisibility()
{
var temp = document.getElementById("iconName").style.visibility;
return temp;
}
function switchIfChecked()
{
var current = getStyle();
if( current == "visible" )
{
document.getElementById("iconName").style.visibility = "hidden";
}
else
{
document.getElementById("iconName").style.visibility = "visible";
}
}
<div id="iconName" style="visibility: visible">INSERT ICON IMG here</div>
What the above does is that it makes the div of the icon visible or hidden. Ofcourse you will need to have two divs and then set either or to hidden or visible.
With what you are doing currently, you are not really making the browser do anything.

Try to use the global option of Regex (have a look at the g after the second slash):
// ...
.replace(/<tr class=\"list-row done\"/g, '<tr class=\"list-row\"')
// ...
Here is an example.
From developer.mozilla.org:
global: Whether to test the regular expression against all possible
matches in a string, or only against the first.

Related

Target word using jQuery and add style

So, this could be a simple question, but my jQuery skills are just not quite up there (yet). I'm trying to target a specific word on a web page and add a style to it. The word I'm trying to target appears multiple times on the page. I googled around some and found this:
<script>
var divContent = document.getElementById("styled").innerHTML;
divContent = divContent.replace("Bevestigd","<span class='styled'>Bevestigd</span>");
divContent = divContent.replace("Geannuleerd","<span class='styled-r'>Geannuleerd</span>");
divContent = divContent.replace("Pending","<span class='styled-p'>Pending</span>");
document.getElementById("styled").innerHTML = divContent;
</script>
This works, kind of... It only targets the first time it encounters the word and doesn't repeat it self. I found numerous pieces of code to target a word on a page but this one seems to work the best... Is there any one that could help me out?
Is there a foreach function I'm missing?
I also tried this code:
$('body').html(
function(i,h){
return h.replace(/(Nike)/g,'<span class="nike tm">$1</span>');
});​
which seems a bit simpler but that didn't work... Maybe someone has a snippet for this or something?
This is your js code done on jquery. Check out this jsfiddle
$('#styled').html(function(i,v){
v=v.replace(/Bevestigd/g,'<span class="styled">Bevestigd</span>');
v=v.replace(/Geannuleerd/g,'<span class="styled-r">Geannuleerd</span>');
v=v.replace(/Pending/g,'<span class="styled-p">Pending</span>');
return v;
});
This is done with regex.
We can extend it further.
eg. To make the search case insensitive use /Bevestigd/gi instead of /Bevestigd/g.

How to add user input to a JavaScript array

I am in the process of learning JavaScript and jQuery, so apologies if any of this sounds naive or obvious. I started what I thought was a fairly simple project to practice and hopefully learn something in the process.
What I want to do is this: the user inputs a sentence and hits a submit button. The sentence gets added to a list of other sentences submitted by people (preferably on a separate file, preferably encrypted, but not necessary). Then, the website grabs a random sentence from the list and displays it.
I am not asking on how to build all of this. I have already put most of it together, but I am including it here for reference.
I have a separate javascript file with the array of quotes.
var quotes=new Array();
quotes[0]="<p>Quote 1</p>";
quotes[1]="<p>Quote 2</p>";
quotes[2]="<p>Quote 3</p>";
quotes[3]="<p>Quote 4</p>";
quotes[4]="<p>Quote 5</p>";
quotes[5]="<p>Quote 6</p>";
quotes[6]="<p>Quote 7</p>";
Then I randomly display one using this:
function getQuote(){
var thisquote=Math.floor(Math.random()*(quotes.length));
document.write(quotes[thisquote]);
}
And adding <script> getQuote(); </script> to the html.
This all works fine.
The part I cannot seem to figure out is taking user input and adding it to the jQuery array. I am using a contenteditable div instead of an <input> because I want it to have multiple lines of text and have a character limit, which as far as I know can only be done with a contenteditable div (according to the research I did at the time, I may be wrong).
I have looked around and tried many if not all the examples I found of how to do this, and none of them worked. This is the last method I tried, if it helps:
$(".submit").click(function() {
quotes[quotes.length] = document.getElementsByClassName("input").value;
});
So, to reiterate, I want to take user input and add it to a JavaScript array. I have scoured stackoverflow and the interet but nothing has worked. Please help!
UPDATE: Arvind got it right. I still have a lot to learn, and it seems I need to read up on localstorage and cookies. I will also need to use PHP to save the sentences on the server. Thank you to all who answered!
Problem is document.getElementsByClassName("input") gives you a NodeList and not just a single html element. So if you do this document.getElementsByClassName("input").value, you will end up quotes as [undefined, undefined ... undefined]. Assuming you have single element with the class name input, go with index 0. Also as you stated that you are using div with attribute contenteditable, you may try this instead. document.getElementsByClassName("input")[0].innerHTML
Try this example.
var quotes = localStorage.getItem('quotes'); //get old, if any, gives you string
quotes = quotes ? [quotes] : []; // if got quotes then make it as array else make new array
$(function() {
var quote = $('#quote'); //get the quote div
quote.html(quotes.join('') || quote.html()); //set the default text
$('#btn').on('click', function(e) {
quotes.push(quote.html());
localStorage.setItem('quotes', quotes.join('')); //save the quotes
alert(quotes.join(''));
});
});
#quote {
border: 1px solid grey;
height: 100px;
overflow: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div contenteditable='' id='quote'>
<ol>
<li>Quote 1</li>
<li>Quote 2</li>
</ol>
</div>
<input type='button' id='btn' value='Submit' />
P.S.
In order to preserve the old quotes you may possibly use cookie, localStorage, etc.
Are these "quotes" being saved locally?
Yes, to share it among several users visiting by different browsers, you have to save it with the server script like PHP, Java, ASP, etc. Here you can either use ajax, if you wana avoid page reload on submit, else you can go for form submit.
$(".submit").click(function() {
quotes[quotes.length] = document.getElementsByClassName("input").value;
});
should be
$(".submit").click(function() {
quotes.push(document.getElementsByClassName("input").text());
});
EDIT: With a content editable div you need to use text() instead. Here is an example fiddle. https://jsfiddle.net/
var quotes=[];// better
// function to add to array
function addQuote(myquote){
quotes.push('<p>'+myquote+'</p>');
}
addQuote("Quote 1");
addQuote("Quote 2");
addQuote("Quote 3");
addQuote("Quote 4");
addQuote("Quote 5");
addQuote("Quote 6");
addQuote("Quote 7");
addQuote("Quote 8");
$(".submit").on('click',function() {
addQuote(document.getElementsByClassName("input")[0].value);
});
NOTE: suggest NOT using the "input" class name and use some other one as that might be confusing to others at some point later (confused by element named input)
I also added the paragraph tags as that would provide a consistent pattern for your input text. Assumption on my part however.
NOTE I also assume that the element IS an input type with the .value since that is NOT provided (the markup)

Eliminate undefined TypeError in self-executing anonymous function

I have a script that gives me the following error: 'TypeError: clickables[ic] is undefined' when I'm checking it with Firebug/in browser consoles. I'm a javascript beginner, who is trying to learn how to do things in vanilla javascript, and so I'm looking specifically for a solution that is just that.
The question: How do I get rid of/silence the undefined TypeError?
What the script should be doing:
I'm using this to reveal hidden elements, whose display attribute is set to none. The script should be getting all the instances of a particular class in a document, .item-reveal, joining that with a unique ID that each item having that class is given, to form a new class to search for via getElementsByClassName. The items with the .item-reveal class are items that are clicked on, the item that is unhidden/revealed has the .ID-reveal-item class (the unique ID of the clickable element followed by the .item-reveal class name reversed, for a simple convention). The ID isn't used for stying at all, it's merely to create a unique class based on a naming convention that can be applied to any pair of elements: one that is clicked on, one that is unhidden/hidden via creating/changing a style for the display attribute.
What the script does:
Currently, the script actually reveals the items onclick, and hides them again on subsequent clicks, and it works with multiple items. So, it kind of, basically, works. I just can't figure out the 'TypeError: clickables[ic] is undefined' issue and how to get rid of it. I get it in several browsers when using developer tools.
The script is an attempt at a self-executing anonymous function sort of thing, so I know the convention is a bit different, but I'm wanting to stick with it so I can apply it to other uses down the road. The article that inspired it is found here:
http://esbueno.noahstokes.com/post/77292606977/self-executing-anonymous-functions-or-how-to-write
EXAMPLE:
HTML
<!-- Item to be clicked, with unique ID -->
<h3 class="item-reveal" id="plan-1">Click for special pricing!</h3>
<p>An introductory paragraph...</p>
<!-- Hidden item to be revealed, will always have a unique class -->
<p class="plan-1-reveal-item">Special, this month only: $49.99</p>
<h3 class="item-reveal" id="plan-b">Click for special pricing!</h3>
<p>An introductory paragraph...</p>
<p class="plan-b-reveal-item">Special, this month only: $29.99</p>
CSS
/* Init - hide/unhide onclicks */
.item-reveal {cursor:pointer;}
[class$="-reveal-item"] {display:none;}
/* Halt - hide/unhide onclicks */
javascript:
var clickables = document.querySelectorAll('.item-reveal');
var clickCount = clickables.length;
(function () {
var Reveal = {
swapThis: function () {
for (var ic = 0; ic <= clickCount; ic += 1) {
// Next line seems to create the error message.
clickables[ic].onclick = function (unhideHide) {
var hidden = this.id;
var findClass = hidden += '-reveal-item';
var revealSwap = document.getElementsByClassName(findClass);
for (rn = 0; rn < revealSwap.length; rn++) {
revealSwap[rn].style.display = (revealSwap[rn].style.display == 'block') ? 'none' : 'block';
}
}
}
}
}
Reveal.swapThis();
}) ();
The script is linked via a SCRIPT tag, just prior to the closing BODY tag. I have tried it with both Async and Defer attributes, with and without other scripts in an HTML document, and the result is the same. I tried adding an event handler to ensure it wasn't something with the DOM loading still ongoing, but I'm not sure how to really test for that to see if it was actually doing anything. Unit testing is something that I'm just starting to attempt familiarizing myself with.
I'm trying to knock the dust off skills after several years in a completely unrelated industry, so the last year has been all about catching up on web development technologies, learning responsive design and HTML5 data stuff, and trying to learn javascript. I've searched, read, and bought several ebooks/books, and this is one of the few times I've run into something I just can't figure out. I imagine it's probably something simple and obvious to someone with formal programming/scripting knowledge, but I was an eBusiness major and networking, marketing, server/systems support, cabling, HTML/CSS, etc., are where I'm comfortable. Any help is greatly appreciated, but keep in mind that I'm trying to implement this in an environment/project that will have no jQuery, by choice. Thanks!
You are going off the end of the list with this:
for (var ic = 0; ic <= clickCount; ic += 1)
Change it to this:
for (var ic = 0; ic < clickCount; ic += 1)
clickCount is the length of the list so since it's 0 based indexing, clickables[clickCount - 1] is the last element in the list. You were trying to access clickables[clickCount] which does not exist.

casperjs, might need another click method?

I am new to casperjs, and as far as I have learned so far, there are only two click methods that can trigger a mouse action:
click() requires a selector
clickLabel() requires "label" between tags
The website I am dealing with right now has dynamic "tabs", by clicking each tab, a javascript submit is triggered, there is no "class", "id" or "label" associated with each tab, except for "pic" element:
<a href="javascript:submitTab('search6')" tabindex="6">
<img src="image6off.gif" name="imag6" height="6" hspace="0" vspace="0" border="0" onmouseover="nbGroup('over','imag6','image6on.gif','image6on.gif',1);" onmouseout="nbGroup('out');" onclick="nbGroup('down','group1','imag6','image6off.gif',1); submitTab('search6')" alt="New Search">
</a>
I tried to use clickLabel() but failed.
YES, I can use XPath, however the problem is the number of tabs is dynamic depending on the available information for each record, so in this case "new search" could be tab 6 for this record but tab 4 in another, tab 8 in yet another.
YES, I could try to write a "loop" to loop through all available tabs, potentially, however, if there is one method of click which combine the
waitForResource()
that would be great, since I can use the "image6on.gif" to tell the program which image or tab to click, apparently, for this website, I found out that each different javascript submit tab program is uniquely associated with one "image#on/off.gif"
I hope some contributor for casperjs can easily implement this method to deal this kind of situation.
Not entirely sure if this is what you want, but you can get the tab based on the tabindex attribute with:
casper.click("a[tabindex='6']");
Edit: Hack I threw together based on your comment below:
casper.thenEvaluate(function() {
var attr = document.querySelector('img[alt="New Search"]').parentNode.getAttribute('tabindex');
__utils__.click('a[tabindex="' + attr + '"]');
});
casper.thenEvaluate() allows you to execute javascript on the remote page.
__utils__ is injected into each page loaded as an extra set of functions that you can use.
I'm not a contributor to CasperJS. From my point of view the clickLabel function is already too much. I cannot remember that I actually used it, because most of the time there is something that prevents an exact string match.
You are right, it is a valid argument to add a new click function to CasperJS. In my opinion it is better to use the provided XPath capability to do that. You can even create the function for your use:
casper.clickByImg = function(imgRes){
var x = require('casper').selectXPath;
this.click(x("//a/img[contains(#href,"+imgRes+")]/.."));
return this;
};
See: minimal overhead.
You can even go this far as to match the image by a regular expression with more overhead.
casper.clickByImgRegexp = function(regexp){
var hrefs = this.getElementsAttribute("a > img", "href");
for(var i = 0; i < hrefs.length; i++) {
if (hrefs[i].match(regexp)) {
this.clickByImg(hrefs[i]);
break;
}
}
return this;
};

replace content of div with another content

I've been building a list of links, all of which should change the content of a div to another specific content (about 4 lines of stuff: name, website, contact etc.) upon a click.
I found this code:
<script type="text/javascript">
function ReplaceContentInContainer(id,content) {
var container = document.getElementById(id);
container.innerHTML = content;
}
</script>
and used it in such a way:
<li class="pl11">
superlink')">Pomorskie</a>
</li>
And it doesn't work as I expected.
It changes hyperlinks text from 'Pomorskie' to 'superlink'.
The plain text works just fine but I need links.
here's the http://xn--pytyfundamentowe-jyc.pl/projektanci/kontakty-p/ (only two of them show anything)
But after trying all of your recomendations, I think I'd jump to different divs with #links, cause nothing worked with this :/
Thanks a lot for trying, and cheers :)
Just as a completely sideways look at this, I'd suggest avoiding the nesting weirdness / complexity, and reducing the problem down.
Setup the content in a hidden (ie. <div id="replacements">...</div>) Grab the innerHTML from the node you want, and be done with it.
Much easier to get replacement content from non-devs that way too, kinda works great if you're in a team.
// Probably better in a separate helpers.js file.
function replaceContentInContainer(target, source) {
document.getElementById(target).innerHTML = document.getElementById(source).innerHTML;
}
Control it with: (lose that href=javascript: and use onClick, better as an event handler, but for brevity I'll inline it as an onClick attribute here, and use a button.)
<button onClick="replaceContentInContainer('target', 'replace_target')">Replace it</button>
We have our target somewhere in the document.
<div id="target">My content will be replaced</div>
Then the replacement content sits hidden inside a replacements div.
<div id="replacements" style="display:none">
<span id="replace_target">superlink</span>
</div>
Here it is in JSBin
Improve the dynamic nature of this by using Handlebars or another nice JS templating library, but that's an exercise for the OP.
edit: Note, you should also name functions with a leading lowercase letter, and reserve the leading uppercase style for Class names e.g. var mySweetInstance = new MySpecialObject();
The quotes are mismatched! So when you click you are getting a JavaScript error.
The browser sees this string:
href="javascript:ReplaceContentInContainer('wojewodztwo', 'superlink')">Pomorskie<
as:
href="javascript:ReplaceContentInContainer('wojewodztwo', '<a href="
Chnage the " inside to #quot;
<li class="pl11">
Pomorskie
</li>
Example fiddle.
Also note, using the href tag for JavaScript is a BAD practice.
You've got a problem with nested quotes. Take a look in your DOM inspector to see what the HTML parser built from it! (in this demo, for example)
You either need to HTML-escape the quotes inside the attribute as " or ", or convert them to apostrophes and escape them inside the JS string with backslashes:
<a href="j[…]r('wojewodztwo', '<a href="http://address.com">superlink</a>')">…
<a href="j[…]r('wojewodztwo', '<a href=\'http://address.com\'>superlink</a>')">…
See working demos here and here.
Better, you should use a onclick attribute instead of a javascript-pseudo-url:
<a onclick="ReplaceContentInContainer('wojewodztwo', …)">Pomorskie</a>
or even a javascript-registered event handler:
<li class="pl11">
<a id="superlink">Pomorskie</a>
</li>
<script type="text/javascript">
function replaceContentInContainer(id,content) {
var container = document.getElementById(id);
container.innerHTML = content;
}
document.getElementBId("superlink").onclick = function(event) {
replaceContentInContainer('wojewodztwo', 'superlink');
event.prevenDefault();
};
</script>
(demo)

Categories