JQuery Chained/Cascading Dropdown events - javascript

I am currently attempting to set up a set of chained selects using the Flexbox Jquery plugin (this is not specifically designed for chaining, but can be used for that).
I have the chaining working if I set everything explicitly, but I am trying to dry my code up and make it more understandable. As such, I have come up with the code below.
All boxes currently load initially, and make their queries. The problem I am having is that when I iterate through the menus (as below), I lose the onSelect functionality - it only fires for the last menu I loaded.
My understanding was that since I am using a different JQuery selector each time - $('#' + fbMenu.divId) - it would not matter that I then set the onSelect behavior for another menu, but evidently this is not the case. Am I somehow overwriting the binding each time I am loading a box?
Hopefully I don't have to specify the onSelect functionality for each dropdown, as there could be a large number of them.
Many thanks for any assistance you can provide!
$(document).ready(function() {
// Create the variables for data objects
var vehicleMakeFb = new Object();
var vehicleModelFb = new Object();
var vehicleTrimFb = new Object();
// Set up each menu with the divId, jsonUrl and the chlid menus that will be updated on select
vehicleMakeFb.divId = 'vehicle_vehicleMake_input';
vehicleMakeFb.jsonUrl = '/vehicles/getmakes';
vehicleMakeFb.children = [vehicleModelFb];
vehicleModelFb.divId = 'vehicle_vehicleModel_input';
vehicleModelFb.jsonUrl = '/vehicles/getmodels';
vehicleModelFb.children = [vehicleTrimFb];
vehicleTrimFb.divId = 'vehicle_vehicleTrim_input';
vehicleTrimFb.jsonUrl = '/vehicles/gettrims';
vehicleTrimFb.children = [];
// Create an array of all menu objects so that they can be iterated through
var allMenus = [vehicleMakeFb,vehicleModelFb,vehicleTrimFb];
// Create the parent menu
for (var i = 0; i < allMenus.length; i++) {
var fbMenu = allMenus[i];
alert(fbMenu.divId);
$('#' + fbMenu.divId).flexbox(fbMenu.jsonUrl + '.json', {
// Update the child menu(s), based on the selection of the first menu
onSelect: function() {
for (var i = 0; i < fbMenu.children.length; i++) {
var fbChild = fbMenu.children[i];
var hiddendiv = document.getElementById(fbMenu.divId + '_hidden');
var jsonurl1 = fbChild.jsonUrl + '/' + hiddendiv.getAttribute('value') + '.json';
alert(jsonurl1);
$('#' + fbChild.divId).flexbox(jsonurl1);
}
}
});
}
});

If you put all the information on the elements them selves i think you will have better results. Although I've been known to be wrong, I think the context of the select functions are getting mixed up.
instead of setting up each menu as an object try:
$(document).ready(function() {
var setupdiv = (function(divId, jsonUrl, children)
{
jQuery('#' + divId)
.data("jsonurl", jsonUrl)
.data("children", children.join(",#"));
   
// Create the parent menu
jQuery('#' + divId).flexbox(jsonUrl + '.json',
{  
// Update the child menu(s), based on the selection of the first menu
onSelect: function()
{  
var children = jQuery(this).data("children");
var jsonUrl = jQuery(this).data("jsonurl");
if(children)
{
children = jQuery('#' + children);
alert('children was true');
}
else
{
children = jQuery();
alert('children was false');
}
var hiddendiv = jQuery('#' + this.id + '_hidden');
children.each(function()
{
var childJsonUrl = jsonUrl + '/' + hiddendiv.val() + '.json';
alert(childJsonUrl);
$(this).flexbox(childJsonUrl);  
});
}
});
});
setupdiv('vehicle_vehicleMake_input', '/vehicles/getmakes', ['vehicle_vehicleModel_input']);
setupdiv('vehicle_vehicleModel_input', '/vehicles/getmodels', ['vehicle_vehicleTrim_input']);
setupdiv('vehicle_vehicleTrim_input', '/vehicles/gettrims', []);
});
DISCLAIMER
I'm known for my spelling mistakes. Please spellcheck before using this code ;)
Update
I've changed the first two lines of code and I've normalized the indenting as there were a mix of tabs and spaces. Should be easier to read now.

Related

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

Limit the input in Fieldset

I am trying to create a section of my webstore where the customer can 'build' their own bundle and choose any combination of 5 items.
I have a set of buttons which, when clicked, add their value to a Fieldset along with a button to remove it in case they misclicked or changed their mind.
All the components work fine, but I don't know how to limit the Fieldset to only five items. Is there a way to either count the lines, then stop accepting input
after five or look for 'Remove' five times?
I'm still fairly new to coding and not too sure what is possible.
This input will end up being submitted in a form.
Here is my Fiddle and below is my Javascript code which i have tried for it :
$(document).ready(function () {
$(".buttons").click(function () {
var intId = $().length + 1;
var item = $(this).html();
var fieldWrapper = $("<div class=\"fieldwrapper\" id=\"field" + intId + "\"/>");
var removeButton = $("<input type=\"button\" class=\"remove\" value=\"Remove\" />");
removeButton.click(function () {
$(this).parent().remove();
});
fieldWrapper.append(size);
fieldWrapper.append(removeButton);
$("#buildyourkit").append(fieldWrapper);
});
});
This will give you the current quantity of elements added to the . Just make sure that there is still room for another before appending a new one.
$("fieldset .fieldwrapper").length
I've forked your fiddle. Just look at the console while adding new items to the fieldset.
You can have a global variable which will count up and disable all buttons if over 5 every time you add a field, and down and enable all buttons every time you remove a field.
Also, it is a bit nicer to just set a live handler listening for any remove buttons, rather than make a new function and bind a new listener for each button, so I demonstrated; but it is not obligatory (your way works, too, given it's just 5 elements).
$(document).ready(function () {
var buttonMaxID = 0;
var buttonCount = 0;
$('$buildyourkit').on('click', '.remove', function () {
$(this).parent().remove();
if (buttonCount-- >= 5) {
$('.buttons').prop('disabled', false);
}
});
$(".buttons").click(function () {
if (++buttonCount >= 5) {
$('.buttons').prop('disabled', true);
}
var item = $(this).html();
var fieldWrapper = $("<div class=\"fieldwrapper\" id=\"field" + (buttonMaxId++) + "\"/>");
var removeButton = $("<input type=\"button\" class=\"remove\" value=\"Remove\" />");
fieldWrapper.append(size);
fieldWrapper.append(removeButton);
$("#buildyourkit").append(fieldWrapper);
});
});
What I propose is designing a manager class to maintain all functions/methods that must interact with the UI. This allows you to define your data set in one place, and keep the UI binds in one place. By doing so, you set yourself up with a cleaner code base, easy refactoring, and quickly make code modifications. Also, you get all this goodness without any global variables, another great bonus.
The code does look like its larger, but once you understand the simplicity of the manager you will see the possibilities I outlined above.
$(document).ready(function () {
//Create a new Kit Manager
var kitManager = new KitManager();
$(".buttons").click(function () {
kitManager.add(this);
});
$(".report").click(function () {
kitManager.getKit();
});
});
function KitManager()
{
//Static amount of items to return
var MAX_ITEMS = 5;
//Where the items should be visually displayed on the UI
var kitLegend = $("#buildyourkit");
//Internal array for storing the items added
var items = []
function add(element)
{
if(items.length < MAX_ITEMS)
{
var itemNumber = items.length + 1;
var item = $(element).html();
var fieldWrapper = $("<div class=\"fieldwrapper\" id=\"field" + itemNumber + "\"/>");
var removeButton = $("<input type=\"button\" class=\"remove\" value=\"Remove\" />");
//Add item to the array collection
items.push(item);
//Bind a remove function to the newly created button
removeButton.click(function () {
kitLegend[0].removeChild(fieldWrapper[0]);
items.splice(itemNumber, 1);
});
//Append UI components to container
fieldWrapper.append(item).append(removeButton);
//Append to main legend
kitLegend.append(fieldWrapper);
}
else
{
//Simple alert to user
alert('You\'ve Reached The Maximum Number of Items');
}
}
//Simple function for demonstration of a reporting feature
//or potential method for returning the stored items
function getKit()
{
for(var i=0,length=items.length;i <length;i++)
{
console.log(items[i]);
}
}
//Expose public method call
return {
add:add,
getKit: getKit
};
}
http://jsfiddle.net/x97S5/
I hope you find the solution acceptable, and if you have any further questions please ask.
For more information on the solution and technique proposed take a look at Key Principles of Maintainable JavaScript

Find ckeditor instance

I want to access and manipulate the content of a textarea where ckeditor is used. My original code before I started using the editor was:
(function ($) {
"use strict";
$(document).ready(function () {
for(var i=0; i<=10; i++) {
$('#edit-button'+i).click(function(){
var tag = $(this).attr("value");
var id ="edit-body-und-0-value"; /* id of textarea */
var element = document.getElementById(id);
var start = element.selectionStart;
var end = element.selectionEnd;
var text = element.value;
var prefix = text.substring(0, start);
var selected = text.substring(start, end);
var suffix = text.substring(end);
selected = "["+tag+"]" + selected + "[/"+tag+"]";
element.value = prefix + selected + suffix;
element.selectionStart = start;
element.selectionEnd = start + selected.length;
return false;
});
}
});
})(jQuery);
This stops working when the editor is enabled.
I'm guessing that I need to use some different object then the 'element' object, the ckeditor object, and then I could maybe use the function described here: http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.editor.html
But how do I get the ckeditor object?
The ckeditor is added in drupal so I know very little about it and I am very unsure about how to access it or what information to look for in order to be able to know what to do.
On this page: http://ckeditor.com/blog/CKEditor_for_jQuery
$( 'textarea.editor' ).ckeditor();
is used to create the object(?). But I already have an ckeditor instance that I need to find. Can I some how select the editor for a given textarea?
Using the jquery adapter, you can get the ckeditor "object" like this:
$('textarea.editor').ckeditorGet()
So to destroy it, you'd do
$('textarea.editor').ckeditorGet().destroy()
This is using version 4.x of ckeditor.

Filtering the list of friends extracted by Facebook graph api ( more of a JavaScript/Jquery question than Facebook API question)

Hello there JavaScript and Jquery gurus, I am getting and then displaying list of a facebook user's friend list by using the following code:
<script>
function getFriends(){
var theword = '/me/friends';
FB.api(theword, function(response) {
var divInfo = document.getElementById("divInfo");
var friends = response.data;
divInfo.innerHTML += '<h1 id="header">Friends/h1><ul id="list">';
for (var i = 0; i < friends.length; i++) {
divInfo.innerHTML += '<li>'+friends[i].name +'</li>';
}
divInfo.innerHTML += '</ul></div>';
});
}
</script>
graph friends
<div id = divInfo></div>
Now, in my Facebook integrated website, I would eventually like my users to choose their friends and send them gifts/facebook-punch them..or whatever. Therefore, I am trying to implement a simple Jquery filter using this piece of code that manipulates with the DOM
<script>
(function ($) {
// custom css expression for a case-insensitive contains()
jQuery.expr[':'].Contains = function(a,i,m){
return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
function listFilter(header, list) { // header is any element, list is an unordered list
// create and add the filter form to the header
var form = $("<form>").attr({"class":"filterform","action":"#"}),
input = $("<input>").attr({"class":"filterinput","type":"text"});
$(form).append(input).appendTo(header);
$(input)
.change( function () {
var filter = $(this).val();
if(filter) {
// this finds all links in a list that contain the input,
// and hide the ones not containing the input while showing the ones that do
$(list).find("a:not(:Contains(" + filter + "))").parent().slideUp();
$(list).find("a:Contains(" + filter + ")").parent().slideDown();
} else {
$(list).find("li").slideDown();
}
return false;
})
.keyup( function () {
// fire the above change event after every letter
$(this).change();
});
}
//ondomready
$(function () {
listFilter($("#header"), $("#list"));
});
}(jQuery));
</script>
Now, This piece of code works on normal unordered list, but when the list is rendered by JavaScript, it does not. I have a hunch that it has to do something with the innerHTML method. Also, I have tried putting the JQuery filter code within and also right before tag. Neither seemed to work.
If anyone knows how to resolve this issue, please help me out. Also, is there a better way to display the friends list from which users can choose from?
The problem is here:
$(list).find("a:not(:Contains(" + filter + "))").parent().slideUp();
$(list).find("a:Contains(" + filter + ")").parent().slideDown();
Since you're rendering this:
divInfo.innerHTML += '<li>'+friends[i].name +'</li>';
There is no anchor wrapper, the text is directly in the <li> so change the first two lines to look in those elements accordingly, like this:
$(list).find("li:not(:Contains(" + filter + "))").slideUp();
$(list).find("li:Contains(" + filter + ")").slideDown();
You could also make that whole section a bit faster by running your Contains() code only once, making a big pact for long lists, like this:
$(input).bind("change keyup", function () {
var filter = $(this).val();
if(filter) {
var matches = $(list).find("li:Contains(" + filter + ")").slideDown();
$(list).find("li").not(matches).slideUp();
} else {
$(list).find("li").slideDown();
}
});
And to resolve those potential (likely really) innerHTML issues, build your structure by using the DOM, like this:
function getFriends(){
var theword = '/me/friends';
FB.api(theword, function(response) {
var divInfo = $("#divInfo"), friends = response.data;
divInfo.append('<h1 id="header">Friends/h1>');
var list = $('<ul id="list" />');
for (var i = 0; i < friends.length; i++) {
$('<li />', { text: friends[i].name }).appendTo(list);
}
divInfo.append(list);
});
}
By doing it this way you're building your content all at once, the <ul> being a document fragment, then one insertion....this is also better for performance for 2 reasons. 1) You're currently adding invalid HTML with the .innerHTML calls...you should never have an unclosed element at any point, and 2) you're doing 2 DOM manipulations (1 for the header, 1 for the list) after the much faster document fragment creation, not repeated .innerHTML changes.

Removing dynamically created child nodes from a form with js / jQuery?

The below post was hugely helpful for what I'm trying to do:
Builing a cart with multiple items and the PayPal button in PHP
This part works beautifully:
var inp1 = document.createElement("input");
inp1.setAttribute("type", "hidden");
inp1.setAttribute("id", "item_number_" + current_item);
inp1.setAttribute("name", "item_number_" + current_item);
inp1.setAttribute("value", current_item);
document.getElementById("paypal-form").appendChild(inp1);
but I'm getting a bit stuck on how to remove an item when needed... I am looking for something like:
document.getElementById('payPalForm').removeChild(inp1);
But obviously I need a way to specify/track those dynamically created id's... or is there an easier way I'm missing?
Any help would be appreciated.
use the parentNode property to retrieve the parent element of an element, and use the removeChild method of the parent:
inp1.parentNode.removeChild (inp1);
Reference:
parentNode property
I'm not sure where this code belongs, but it would be helpful to encapsulate this within a class.
function NewClass() {
var formPP = document.getElementById('payPalForm');
var lInputs = [];
this.addItem = function(val) {
var inp1 = document.createElement("input");
inp1.setAttribute("type", "hidden");
inp1.setAttribute("id", "item_number_" + val);
inp1.setAttribute("name", "item_number_" + val);
inp1.setAttribute("value", val);
formPP.appendChild(inp1);
lInputs.push(inp1);
}
this.removeItem = function(val) {
var e = formPP.firstChild;
while( e ) {
var eNext = e.nextSibling;
if( e.value == val )
formPP.removeChild(e);
e = eNext;
}
}
}
You'll have to tailor this to meet your needs, but hopefully this gives you a start.

Categories

Resources