One function for many buttons - javascript

I have dynamically created elements on the page, a picture and three buttons which are created upon clicking the main button.
All of this works, but now I am trying to change the display on the dynamically created div with the pics to "none".
More than one issue arises here for me, first I cannot find out how to make the div "images" the target, or select it.
I am trying to get one function to do this for all the elements, they are all structured equally just the pictures are different.
function hidePic(arrayPos){
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
document.getElementsByClassName("closingButton")[0].addEventListener("click", function(){
hidePic(0);
});
This is the relevant code, lines 4 to 10. If this is commented out, the rest of the code works, but as it is I get entirely unrelated errors in dev Tools.
Click this link to see Codepen.
So the question is, how can I best implement the above code?

So just working on the code above you can do this in order to make it work for all instances. First let me point out that this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]";
will never work. That line is building a string. What you really want to make that line work is:
var elem = document.getElementsByClassName("closingButton")[arrayPos];
But even that I find unnecessary. Take a look at this code.
function hidePic (elem) {
var finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
var closingButtons = document.getElementsByClassName("closingButton");
var index = 0, length = closingButtons.length;
for ( ; index < length; index++) {
closingButtons[index].addEventListener("click",
function () {
hidePic(this);
}
);
}
This first finds all elements with the class closingButton. Then for each one we attach a click event listener. Instead of attempting to pass some index to this hidePic function we already have our function context which is what you seem to be trying to find in the function so lets just pass that and use it to find the image inside.
Let me know if you have any questions. I took a look at your codepen as well. I am not sure you should be forcing all that interactive HTML into a button element honestly, which itself is considered an interactive element. Not sure that meets the HTML spec. Perhaps add that HTML below the button. I bet when you click on things inside of that button it will register as clicks on the button as well unless you remove the event upon inserting your elements but then it seems like its getting too complicated for the simple stuff you are trying to do here.

The codepen complains because there is no element with the "closingButton" class, so it's trying to call addEventListener on nothing, but I'm doubting that's the actual error you're seeing.
It's also worth nothing that I think this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
is excessive.
var elem = document.getElementsByClassName("closingButton")[arrayPos];
should be sufficient. Also not the syntax error at the end of the same line: it should be ; not ,. If this is the error in your code it could explain why you were getting "unrelated errors" syntax errors can cause misleading problems that are supposedly in other areas of the code!
Lastly, I'd highly recommend using JQuery to do your selection magic - it's exactly what it was designed for. If you're averse to using JS libraries, fair enough, but it would make your code a lot simpler and you can have reasonable confidence that it will perform the tasks about as optimally as is possible.

Related

How to get jquery selector seeing updated dom in change handler

I'm fairly new to javascript and jQuery. I've searched for answers to this question, but have had no luck, though I bet there are some in here. So advance apologies if this is a dup.
Markup has 3 checkboxes with different classes, and one class in common. I want to notice when the number of boxes checked in either of two classes changes, or rather when there is a transition between at least one box in two of the classes being checked or unchecked. The two interesting classes are named "professional" and "vendor", and the class in common is "account_type_checkbox".
When the page is ready, I count the number of checked "professional" and "vendor" boxes with:
jQuery("input.professional[checked='checked'], input.vendor[checked='checked']").length
This appears to work correctly. I have a "change" event handler on checkboxes in the common class that does the same count when it triggers. But when the event triggers, it gets the same count as it did on page load - i.e. it doesn't see the updated DOM with the modified checked attribute.
I've put a jsfiddle for this at http://jsfiddle.net/cm280s9z/1
Could someone please help me fix this, and/or explain why my code doesn't work the way I expected it to?
http://jsfiddle.net/cm280s9z/3/
Use alert($(":checkbox:checked").length); to get the sum of all marked checkboxes.
There are several other ways of doing this too, as pointed out in this thread, such as doing it by classes on a checkbox:
calculate the number of html checkbox checked using jquery
Maybe you will find this useful: http://jsfiddle.net/cm280s9z/6/
Here's a cleaned up version (not saying it's the best ever) of what you had, showing the :checked.
Reasons why this code is good:
storing the jQuery object checkboxes means it won't have to re-jquery-objectify it every time.
grabbing objects by certain [vague or lengthy] selectors can be more strenuous on jQuery. Grabbing by this class means it'll be more specific as well. We can further filter out checked using .filter. Extra Tip: If traversing the DOM, I like to grab a container that's fairly unique and use .find() to help me get at the descendants.
functions can bring some order and organization to what you're doing.
comments are your friend.
Hope this helps!
var GLOB = GLOB || {};
jQuery(document).ready(function () {
// Define
var checkboxes = jQuery('.account_type_checkbox');
var get_checkbox_count = function(checkboxes){
return checkboxes.filter(':checked').length;
};
var do_business = function(){
alert('transitioned to business');
};
var do_personal = function(){
alert('transitioned to personal');
};
// Initialize
GLOB.business_count = get_checkbox_count(checkboxes);
alert('GLOB.business_count = ' + GLOB.business_count);
// Events
checkboxes.change(function(){
var cur_count = get_checkbox_count(checkboxes);
var add_business = (cur_count > 0);
var no_business = (GLOB.business_count < 1);
// If any are selected it's business, where previously none were checked.
var transition_business = (add_business && no_business);
// If none are selected it's personal, if previously any were checked.
var transition_personal = (!add_business && !no_business)
if (transition_business)
do_business();
if (transition_personal)
do_personal();
});
});

How to create a string and use a function that's named after it?

Sorry for bad wording in the question but it's hard to explain for me. I'm using several bxsliders on a page and some are placed in hidden divs. Unfortunately images are not shown in the slider after making the parent div visible unless the slider is reloaded (See here: bxSlider within show/hide divs). So let's say I initiate the sliders at the beginning with:
var slider_0=$("#slider_0 .bxslider").bxSlider({
//bxslider options here
});
var slider_4=$("#slider_4 .bxslider").bxSlider({
//bxslider options here
});
var slider_7=$("#slider_7 .bxslider").bxSlider({
//bxslider options here
});
The sliders are not consecutively numbered but there is a navigation and if I click the 7th element it leads to slider_7. So I could get the index of the clicked item with:
$(this).index();
When I call slider_7.reloadSlider(); it would work but I don't know which slider the user clicks and which number it has. So would it be possible to call that with a created string like this:
slider_name='slider_'+$(this).index();
slider_name.reloadSlider();
works not of course. Is there a way to do it?
I would create a dictionary with strings as keys and functions as values. Then, you could have O(1) lookup of the functions you're targeting.
In general, you can do it like so:
// set up your dictionary
var dictionary = {};
// add your functions
dictionary['methodName'] = function() {};
// call the functions
dictionary['methodName']();
So, for your example, you could do:
dictionary['slider_7'] = slider_7.reloadSlider;
dictionary['slider_'+$(this).index()]();
You could trigger it with
window["slider_" + $(this).index()].reloadSlider()
Although, I'm not sure whether your approach is the best. I think I'd go with arrays or with object (as a key-value pairs)
Try this:
slider_name='slider_'+$(this).index();
$("#" + slider_name + " .bx_slider").reloadSlider();
Found a working solution:
eval("slider_" + $(this).index()).reloadSlider();
Its not entirely clear here what you want/are trying to do. What it seems like you want to do is get a programmatic handle on a specific slider when a user clicks a specific part of your page. You do not accomplish this by eval()ing a string...that's what event handlers are for. So create a click event handler and in that event handler
$('#idOfWhatTheUserClicksOn').click(function(event) {
var slider = document.getElementById('#idOfRelatedSlider');
$(slider).bxSlider();
//if you need the current value of the slider you can get that too
var value = slider.value;
});
You could achieve the same with fewer LOC by using a class instead of id's with different handlers, but the concept is the same.
var slider_cache = [
$("#slider_0 .bxslider").bxSlider(),
$("#slider_1 .bxslider").bxSlider(),
$("#slider_2 .bxslider").bxSlider()
];
...
slider_cache[$(this).index()].reloadSlider();

JQuery & Javascript: Intermittant mis-matching of clicked div and returned div?

Thanks for giving this a look. I'll start with a quick image. Clicking on any of the red-boxed search results seems to return the <div> for the item directly above it.
Here I clicked on 1613 CAROUSEL CIR, but the event returned the id/content for the item representing 1612..
Sometimes it's even weirder, for example, every item following 1420 might point back to 1420. So it's not always a conflict with a <div> and it's immediate neighbor, although that's usually the case.
I've been unable to find any definite pattern in this behavior. Sometimes it's just one or two items in the list; sometimes most of the list is affected, with "a good portion" of results pointing to one particular div.
There's only one true consistency--typically the first several items work as expected, and short lists will be 100% correct. But really long lists (50+) are not necessarily worse than semi-long lists (20+).. :/
The code building the search results iterates over JSON data retrieved by a JQuery $.ajax() call, and this is the relevant code building the visible search results:
if( result.d.length > 0 )
{
var i=0;
for(i; i<result.d.length; i++)
{
// ..there's a bunch of irrelevant code here to set the map bounds..
// ..then I build the HTML using JQuery like this
//
var date = new Date();
var divID = "searchItemDiv" + date.getTime().toString();
var $searchItemDiv = $( "<div id='" + divID + "' class='searchItemDiv'>"+result.d[i].Description+"</div>" );
$searchItemDiv.data('itemData', result.d[i]);
$searchItemDiv.bind('click', onSearchItemClick);
$( "#searchResults" ).append($searchItemDiv);
}
}
While I don't suspect the event handler is the issue, the relevant code there looks like this:
function onSearchItemClick(event)
{
if( event.target.id.toString() !== '' )
{
// I clicked 1613, but event returned DIV with text of "1612"??
//
var item = $('#'+event.target.id.toString()).data('itemData');
alert( event.target.id.toString()+"\n"+
$('#'+event.target.id.toString()).text() );
// ..more irrelevant stuff to show a popup of property data..
}
}
FireFox, Chrome, and IE all demonstrate the same behavior, so it's not browser-specific.
I'm relatively sure this is not the product of a race condition during the render phase, but I'm not comfortable-enough with JavaScript to know that for certain.
I'm pretty baffled by this. FWIW, I'm a former Flex & C# developer and relatively new to JavaScript/JQuery development, so there may be a gotcha related JavaScript contexts and/or JQuery that I'm stepping into.
I would say, instead of binding the click function within a for-loop, just select all of the searchItemDiv's after the for-loop binds the data to them, and register a click function on all of them at once. You don't need a separate line to define variable i, just do it in the for statement. I also wouldn't try to generate random IDs with new Dates, that just seems unnecessary. Registering all click functionality at once will also make your click handler much simpler:
if( result.d.length > 0 )
{
for(var i = 0; i<result.d.length; i++)
{
// ..there's a bunch of irrelevant code here to set the map bounds..
// ..then I build the HTML using JQuery like this
// select the i'th searchItemDiv
$searchItemDiv = $($('.searchItemDiv')[i])
// give it the data
$searchItemDiv.data('itemData', result.d[i]);
$( "#searchResults" ).append($searchItemDiv);
}
// then register all the click handlers at once, very simple
$('.searchItemDiv').bind('click', function() {
var item = $(this);
alert(item.text());
});
}
--EDIT--
also, do the searchItemDivs already exist or are you trying to create them?
if you're trying to create them, you might want this in the for-loop instead:
for(var i = 0; i<result.d.length; i++)
{
// ..there's a bunch of irrelevant code here to set the map bounds..
// ..then I build the HTML using JQuery like this
// create a searchItemDiv
$searchItemDiv = $('<div class="searchItemDiv"></div>')
// give it the data
$searchItemDiv.data('itemData', result.d[i]);
$( "#searchResults" ).append($searchItemDiv);
}
I'm guessing that is what you want to do.
I think your problem depends on your searchItemDiv id.
Using the date doesn't ensure ids are unique so when you retrieve the object by id it will return an element (probably the first) with the same id.
Make sure to assign unique id on your elements.

Jquery/Javascript - Syntax highlighting as user types in contentEditable region

I'm developing a contentEditable region on my website, where users will be able to type messages to each other.
<div contentEditable="true" class="smartText">User types here...</div>
The thing is, we will have smart text inside, meaning that if a user type #usersame inside this div, the #username should be highlighted in blue if the username exist and green if he doesn't exist. And of course all of this should happen as the user types...
I have no idea where to start, right now I have this:
$("body").on("keyup",".smartText",function(){
var $this = $(this),
value = $this.html(),
regex = /[^>]#\S+[^ ]/gim;
value = value.replace(regex,"<span style='color:red'>$&</span>");
$this.html(value);
});
But the text keeps jumping (as well as the caret position) and doesn't feel like the right direction. I guess it's a little similar to JSFiddle which colors code as it finds it.
I basically want the same thing as Twitter has.
Here is a JSFiddle to play around with: http://jsfiddle.net/denislexic/bhu9N/4/
Thanks in advance for your help.
I liked this problem and I worked very hard to solve. I believe I have finally succeeded (with a little assistance).
= UPDATED =
Piece of Code:
[...]
// formatText
formatText: function (el) {
var savedSel = helper.saveSelection(el);
el.innerHTML = el.innerHTML.replace(/<span[\s\S]*?>([\s\S]*?)<\/span>/g,"$1");
el.innerHTML = el.innerHTML.replace(/(#[^\s<\.]+)/g, helper.highlight);
// Restore the original selection
helper.restoreSelection(el, savedSel);
}
[...]
// point
keyup: function(e){
// format if key is valid
if(helper.keyIsAvailable(e)){
helper.formatText($this[0]);
}
// delete blank html elements
if(helper.keyIsDelete && $this.text()=="") {
$this.html("");
}
}
Screenshot:
JSFiddle here: http://jsfiddle.net/hayatbiralem/9Z3Rg/11/
Needed External Resources:
http://dl.dropboxusercontent.com/u/14243582/jscalc/js/rangy-core.js
http://dl.dropboxusercontent.com/u/14243582/jscalc/js/rangy-selectionsaverestore.js
Helper Question (thanks): replace innerHTML in contenteditable div
Regex Test Tool (thanks): http://www.pagecolumn.com/tool/regtest.htm
Keep in mind that the HTML markup typed by the user could be quite surprising, e.g: <span>#use</span><span>rname</span>, which still looks like #username to the user.
To avoid the crazy caret behavior (and some other nasty side effects) inside a contentEditable element, you should use W3C DOM API and walk the DOM tree each time there is a change in HTML (you can sniff the change by polling body.innerHTML upon a timer).
I've recently answered a similar question for CKEditor and described the algorithm of how to build a text-to-node map of the DOM, for finding a text match. The CKEditor DOM API is quite similar to the W3C one, you can adapt the same algorithm.
Once the match has been found, you should use DOM Range API to manipulate the content of the DOM nodes. E.g., to wrap a run of plain text with a styled <SPAN>:
var range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
var span = document.createElement("span");
span.style.backgroundColor = "blue"
range.surroundContents(span);
Overall, this task is quite non-trivial and certainly isn't something you can fit into a single page of JavaScript code, to be answered here.
This seems to be somewhat a solution to your problem.
DEMO here: http://jsfiddle.net/bhu9N/5/
$(document).ready(function() {
$("body").on("keyup", ".editable", function(e) {
var $this = $(this);
if(e.keyCode==32) {//space
var words = $this.text().split(' ');
var lastword = $.trim(words[words.length-1]);
var reg = /^#\S+/;
if(reg.test(lastword)) {
//make an AJAX call for checking this word for existence
//suppose data is returned and data==1 means green
var data = 1;
if(data==1) {
var orgtext = $this.html();
orgtext = orgtext.replace(lastword, '<span class="green">'+lastword+'</span>');
$this.html(orgtext);
}
}
}
});
});​
Once the text is highlighted, the cursor goes to the starting of the div. So this still needs to be fixed. I will be updating the solution if I am able to find it. Meanwhile, just play around with what I have provided now and see if it helps.
As Ezos pointed out in his answer, I would not recommend trying to do anything intensive (such as making Ajax requests to check if a username exists or not) each time the user releases a key. You might have a bad time. With that said, I would recommend waiting a set amount of time after the user has stopped typing to run through what they've typed and highlight words, for example:
var textarea = $(".smartText");
var highlightWords = function highlightWords() {
var original = textarea.text();
var replaced = original.replace(/#[a-zA-Z0-9]+/g, function (username) {
// Magic
return "<span class='exists'>" + username + "</span>";
});
textarea.html(replaced);
};
var timer;
textarea.keyup(function (e) {
clearTimeout(timer);
if ($(this).text()) {
timer = setTimeout(highlightWords, 1000);
}
});
Link to a fiddle: http://jsfiddle.net/neJLW/
I think the code above should get you started in the right direction. Like you said, the cursor will still jump around so you'll have to save it and reset it in its old position each time you edit the contents of the div. Also, you'll want to adjust the timeout according to how long you expect determining if a username exists to take. You'll need to replace // Magic with your username check and adjust the return value accordingly.
As an aside, you'll want to keep in mind the accessibility issues with wrapping certain things in spans (see this GitHub issue for Lettering.js for an example).
Edit: Also note that this is not a robust solution (it doesn't react to copy paste for example). YMMV.
The method you are using seems very browser intensive and may cause some issues if someone types very quickly and it's running multiple requests before the 'String' can be verified through ajax. You might be better off if you use a library such as http://aehlke.github.io/tag-it/ - You can depict a function to change font color, etc, the same way it recommends a tag.
If i get time, i will make fiddle demo.

JQuery hide/show gone wrong

Here is a simplified example of what i'm working with, working around preexisting code:
Basically I have 2 divs I want to hide/show in multiple places(stage 1, stage 2, stage 3,etc), as so:
var blue_div = "#Blue";
var red_div = "#Red";
var blue_stage = "#Blue" + count;
var red_stage = "#Red" + count;
Adding insult to injury the div's exist elsewhere on page and are hidden. Need to pull the content into another div for each stage. So i'm using .prepend() to grab the content, as so:
var blue_html = $(blue_div).html();
var new_div = "#new_div";
$(new_div).prepend(blue_html);
$(new_div).attr('id', blue_stage); //Changing the id based on the stage
That last part is really whats throwing me...As now I'm trying to use the new_div without first ending the script so it's not yet in the DOM...
if ($(blue_stage).is(':hidden')) {
$(blue_stage).show()
$("#cancel").bind("click",function(){
$(blue_stage).hide()
}
}
I've seen a lot done with Window setTimeout() as well as setinterval and .queue(). But my attempts have all failed. Hopefully my example wasn't confusing, any help at all is appreciated!
I think you can do something like this:
var $new_div = $('<div id="' + blue_stage + '"></div>');
which will allow you to edit the element directly so you can do things like:
$new_div.prepend(blue_html);
to change the id you do:
$new_div.attr('id', blue_stage)
and note when your setting the id like this you don't need the "#" as the other answer mentions
Remember that you use the hash-mark # when selecting, but when setting as an ID on a node, you just use the identifier without this mark. So this line:
$(new_div).attr('id', blue_stage); //Changing the id based on the stage
Equates to this:
$(new_div).attr('id', '#Blue' + count);
But should perhaps be like this:
$(new_div).attr('id', 'Blue' + count);
(without the hashmark).
Hopefully your problem is as easily solved! Good luck!

Categories

Resources