I'll try to keep it at simple as I can.
I have a JSON object that is pulled via AJAX. I am displaying a list of icons in a main div from the data dynamically which can be toggled on or off.
I have a secondary div where the selected items are appearing, while the main div icon receives a class of active.
I want the end user to be able to remove any of them by clicking on them on either the main div or secondary div.
Most of this is working, but I'm having trouble figuring out the best way to map them together so that I have 2 separate click events which can control the same outcome.
I think it may have something to do with the fact that I'm dynamically creating elements... which create more elements... which have to alter the initial elements.
My structure so far is to map the current selection inside of an array. This gives me control over keeping a code-based list of everything that is selected (there is much more data than in the example I'll be providing).
So, here is how I have it so far:
HTML:
<div id="options"></div>
<div id="selectedOptions"></div>
Javascript/jQuery:
// Simple simulated AJAX data
var ourData = {
"color1": "yellow",
"color2": "blue"
};
var $options = $('#options');
var $selectedOptions = $('#selectedOptions');
// Array to keep track of which objects are selected
var selectedOptions = [];
// Makes the initial option dynamic list
makeOptions(ourData, $options);
// If an option from the main div is clicked, handle class changing
$('button').on('click', function(){
pickOption($(this));
});
/* This function is the problem. The option gets removed from the array, and from the secondary div, but the class of active still occurs on the main div. */
$selectedOptions.on('click', '.optionActive', function(){
var option = $(this).data('color');
var optionPosition = jQuery.inArray(option, selectedOptions);
selectedOptions.splice(optionPosition, 1);
displayOptions(selectedOptions, $selectedOptions);
});
// Creates initial icons (buttons in this case) to the DOM and applies a data-attribute for the color
function makeOptions(options, $container){
var $results = $('<div id="results">');
$.each(options, function(key, value){
var $optionButton = $('<button>' + key + ':' + value + '</button>');
$optionButton.data('color', value);
$results.append($optionButton);
});
$container.append($results);
}
/* Handler for selecting an option from the Main Div. Handling the class active.
I am not using a simple classToggle because there are many situations where a click is not allowed */
function pickOption($option){
var selectedOption = $option.data('color');
// If the option returns true, or that it doesn't exist yet
if(modifyOptions(selectedOption, selectedOptions)){
$option.addClass('active');
} else {
$option.removeClass('active');
}
// Recreate our current selected options
displayOptions(selectedOptions, $selectedOptions);
}
/* Searches array to see if the option exists. Returns true or false and either pushes or splices the option from the array */
function modifyOptions(option){
var optionPosition = jQuery.inArray(option, selectedOptions);
if(optionPosition == -1){
selectedOptions.push(option);
return true;
} else {
selectedOptions.splice(optionPosition, 1);
return false;
}
}
/* Displays all currently selected options that exist in our array */
function displayOptions(selectedOptions, $container){
$container.empty();
$.each(selectedOptions, function(option, value){
var $optionTile = $('<div class="optionActive">');
$optionTile.data('color', value)
.text(option + ":" + value)
.css('background', value);
$container.append($optionTile);
});
}
So, to summarize, I want some some way to remove the .active class from the main div equivalent element when the item from the second div is clicked.
I tried removing the class active by searching for any elements with the data-color=data-color of the selected item, but I couldn't get that to work.
ex:
$('*[data-color="' + $(this).data('color') + '"]').removeClass('active');
I would really like some data approach to this, such as removing the class active if it had data-color="yellow" for instance.
Playground:
https://jsfiddle.net/c75xcLha/
EDIT:
Both Are Selected, working as designed:
Clicked Yellow Div. Yellow Button is still active:
Should remove the active class from the button when the yellow div OR the button is pressed, as shown here:
You are assigning data-* property using .data(PROP), not attribute hence element having data-* property could not be accessed/selected using attribute-selector, assign attribute using .attr('data-color') instead of .data(property)
Attribute Equals Selector [name=”value”], Selects elements that have the specified attribute with a value exactly equal to a certain value.
.data( key, value ), Store arbitrary data associated with the matched elements.
When you use .data() to update a data value, it is updating internal object managed by jQuery, so it will not be updated in the data-* attribute[Ref]
// Simple simulated AJAX data
var ourData = {
"color1": "yellow",
"color2": "blue"
};
var $options = $('#options');
var $selectedOptions = $('#selectedOptions');
// Array to keep track of which objects are selected
var selectedOptions = [];
// Makes the initial option dynamic list
makeOptions(ourData, $options);
// If an option from the main div is clicked, handle class changing
$('button').on('click', function() {
pickOption($(this));
});
/* This function is the problem. The option gets removed from the array, and from the secondary div, but the class of active still occurs on the main div. */
$selectedOptions.on('click', '.optionActive', function() {
var option = $(this).data('color');
var optionPosition = jQuery.inArray(option, selectedOptions);
selectedOptions.splice(optionPosition, 1);
$('[data-color="' + $(this).data('color') + '"]').removeClass('active');
displayOptions(selectedOptions, $selectedOptions);
});
// Creates initial icons (buttons in this case) to the DOM and applies a data-attribute for the color
function makeOptions(options, $container) {
var $results = $('<div id="results">');
$.each(options, function(key, value) {
var $optionButton = $('<button>' + key + ':' + value + '</button>');
$optionButton.attr('data-color', value);
//___________^^^^^^^^^^^^^^^^^^Little trick here!
$results.append($optionButton);
});
$container.append($results);
}
/* Handler for selecting an option from the Main Div. Handling the class active.
I am not using a simple classToggle because there are many situations where a click is not allowed */
function pickOption($option) {
var selectedOption = $option.data('color');
// If the option returns true, or that it doesn't exist yet
if (modifyOptions(selectedOption, selectedOptions)) {
$option.addClass('active');
} else {
$option.removeClass('active');
}
// Recreate our current selected options
displayOptions(selectedOptions, $selectedOptions);
}
/* Searches array to see if the option exists. Returns true or false and either pushes or splices the option from the array */
function modifyOptions(option) {
var optionPosition = jQuery.inArray(option, selectedOptions);
if (optionPosition == -1) {
selectedOptions.push(option);
return true;
} else {
selectedOptions.splice(optionPosition, 1);
return false;
}
}
/* Displays all currently selected options that exist in our array */
function displayOptions(selectedOptions, $container) {
$container.empty();
$.each(selectedOptions, function(option, value) {
var $optionTile = $('<div class="optionActive">');
$optionTile.data('color', value)
.text(option + ":" + value)
.css('background', value);
$container.append($optionTile);
});
}
.active {
background: #CCF;
}
.optionActive {
min-width: 100px;
min-height: 100px;
display: inline-block;
margin: 1em;
background: #eee;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id="options"></div>
<div id="selectedOptions"></div>
Updated Fiddle
Edit: If you still want to stick with .data method, use .filter to select target element.
$('button').filter(function(){
return $(this).data('color')=='yellow';
}).removeClass('active');
Related
I have developed an Android app, which consists in a listview of items populated from strings, which changes the color of each list row depending on a word coincidence.
Now i'm trying to develop same app, for the web. After being investigating, the best way I did found, was using JQuery Mobile.
So, now I want to accomplish the same, a ListView that conditionally changes each list item background-color conditionally.
After several days investigating and learning, I'm populating the list from a JSON, like you can see here in JSFiddle (This is what I've achieved until now, based on another JSFiddle I did found, because I had never used JQuery Mobile.)
//JSON goes above here
$(document).on("pageinit", "#info-page", function () {
//set up string for adding <li/>
var li = "";
//container for $li to be added
$.each(info, function (i, name) {
//add the <li> to "li" variable
//note the use of += in the variable
//meaning I'm adding to the existing data. not replacing it.
//store index value in array as id of the <a> tag
li += '<li>' + name.Número + '<p>' + name.Origen + '</p></li>';'</a></li>';
});
//append list to ul
$("#prof-list").append(li).promise().done(function () {
//wait for append to finish - thats why you use a promise()
//done() will run after append is done
//add the click event for the redirection to happen to #details-page
$(this).on("click", ".info-go", function (e) {
e.preventDefault();
//store the information in the next page's data
$("#details-page").data("info", info[this.id]);
//change the page # to second page.
//Now the URL in the address bar will read index.html#details-page
//where #details-page is the "id" of the second page
//we're gonna redirect to that now using changePage() method
$.mobile.changePage("#details-page");
});
//refresh list to enhance its styling.
$(this).listview("refresh");
});
});
//use pagebeforeshow
//DONT USE PAGEINIT!
//the reason is you want this to happen every single time
//pageinit will happen only once
$(document).on("pagebeforeshow", "#details-page", function () {
//get from data - you put this here when the "a" wa clicked in the previous page
var info = $(this).data("info");
//string to put HTML in
var info_view = "";
//use for..in to iterate through object
for (var key in info) {
//Im using grid layout here.
//use any kind of layout you want.
//key is the key of the property in the object
//if obj = {name: 'k'}
//key = name, value = k
info_view += '<div class="ui-grid-a"><div class="ui-block-a"><div class="ui-bar field" style="font-weight : bold; text-align: left;">' + key + '</div></div><div class="ui-block-b"><div class="ui-bar value" style="width : 75%">' + info[key] + '</div></div></div>';
}
//add this to html
$(this).find("[data-role=content]").html(info_view);
});
So, basically what I want is to change (if it is possible) the colour of each row, depending of the of the word under the row title (or any other variable I could include in the JSON, would only be three variables):
This is what i have in Android, just to clarify what I want:
if (additiveslist.get(position).getOrigen().equals("Vegano")) {
holder.relativeLayout.setBackgroundColor(0xB790D55D);
}
if (additiveslist.get(position).getOrigen().equals("Dudoso")) {
holder.relativeLayout.setBackgroundColor(0x96F6B22D);
}
if (additiveslist.get(position).getOrigen().equals("No vegano")) {
holder.relativeLayout.setBackgroundColor(0x84f51000);
}
And this is how it looks like on Android App:
Hope I explained well and someone can help me, because I am a complete beginner in JQuery Mobile (or maybe I did wrong choosing JQuery Mobile to do this kind of web app...)
You can create CSS classes for each of the background colors e.g.:
.vegano {
background-color: #ABDD87 !important;
}
.dudoso {
background-color: #F5CB98 !important;
}
.novegano {
background-color: #F47D75 !important;
}
Then in the script when you are iterating the data, add the appropriate class to the anchor within the LI based on your criteria, e.g.:
$.each(info, function (i, name) {
//add the <li> to "li" variable
//note the use of += in the variable
//meaning I'm adding to the existing data. not replacing it.
//store index value in array as id of the <a> tag
var bColor = "vegano";
if (name.Origen == "Dudoso") {
bColor = "dudoso";
} else if (name.Origen == "No vegano") {
bColor = "novegano";
}
li += '<li>' + name.Número + '<p>' + name.Origen + '</p></li>';'</a></li>';
});
Here is your updated FIDDLE
P.S. Once you start changing the backcolor, you might want to get rid of the default jQM text shadows with this CSS:
#prof-list li a{
text-shadow: none;
}
I have a TableSorted table, and in each TD element are five SPAN elements. Four are always hidden, but upon clicking a div outside the table, certain spans are hidden dependent on which div is clicked.
I have the table sorting fine, but what I need is for the textExtraction to grab a different SPAN, depending on the value of the div which has been selected.
I've tried the following to no avail:
textExtraction:function(node){
var filter=$("div.career a.sel").text();
if(filter=="a"){var theindex=0;}
if(filter=="b"){var theindex=1;}
if(filter=="c"){var theindex=2;}
if(filter=="d"){var theindex=3;}
if(filter=="e"){var theindex=4;}
return $(node).find("span").eq(theindex).text();
}
What is the best way to achieve this?
The textExtraction function is only called when tablesorter is initialized or updated. Try triggering an update after the selection has changed.
var indexes = 'abcde'.split(''),
// Is this a select box?
$sel = $("div.career a.sel").on('change', function(){
$('table').trigger('update');
});
$('table').tablesorter({
textExtraction: function(node){
// if this is a select, get val(), not text()
var filter = $sel.val(),
theindex = $.inArray( filter, indexes );
return $(node).find("span").eq(theindex).text();
}
});
Update: for a link, try this:
var indexes = 'abcde'.split(''),
$careers = $("div.career a").on('click',function(){
var searcher = $(this).attr("rel");
$("div.career a").removeClass("sel");
$(this).addClass("sel");
$("td.stat span").hide();
$("td.stat span.career_" + searcher).show();
});
$('table').tablesorter({
textExtraction: function(node){
// find selected
var filter = $careers.filter('.sel').text(),
theindex = $.inArray( filter, indexes );
return $(node).find("span").eq(theindex).text();
}
});
Note: Don't use live() in jQuery version 1.9+, it was removed.
I am building some custom dropdown controls and the z-index isn't working properly.
// Add the empty class to the container div if no check boxes are checked.
$('.qx-container').each(function ()
{
var container = $(this);
if (!container.find('input[type="checkbox"]').is(':checked'))
{
container.find('.qx-content').text($('.qx-content').attr('empty-message'));
container.find('.qx-content').addClass('qx-empty-content');
}
else
{
handleCheckBoxToggle(container.find('input[type="checkbox"]'));
}
});
// Wire a mouse enter event to the container div. Turns the drop-down list's colors to gray if the slider isn't visible.
$('.qx-container').mouseenter(function ()
{
var container = $(this);
if (!container.find('.qx-slider').is(':visible'))
{
container.find('.qx-container-border-outer').addClass('qx-container-border-outer-hover');
container.find('.qx-container-border-inner').addClass('qx-container-border-inner-hover');
container.find('.qx-container-border-background').addClass('qx-container-border-background-hover');
}
container.data('hoverState', true);
});
// Wire a mouse leave event to the container div. Turns the drop-down list's colors to white if the slider isn't visible and
// sets the container div's empty class if no check boxes are checked.
$('.qx-container').mouseleave(function ()
{
var container = $(this);
if (!container.find('.qx-slider').is(':visible'))
{
container.find('.qx-container-border-outer').removeClass('qx-container-border-outer-hover');
container.find('.qx-container-border-inner').removeClass('qx-container-border-inner-hover');
container.find('.qx-container-border-background').removeClass('qx-container-border-background-hover');
}
if (container.text() == '')
{
container.text($(this).attr('empty-message'));
container.addClass('qx-empty-content');
}
container.data('hoverState', false);
});
// Wire a click event to the content div. Shows or hides the slider and changes the drop-down list's colors based on the slider's visibility.
$('.qx-container-border-outer').click(function ()
{
var outer = $(this);
var inner = $(this).find('.qx-container-border-inner');
var background = $(this).find('.qx-container-border-background');
var container = outer.closest('.qx-container');
var slider = container.find('.qx-slider');
var sliders = $('.qx-container').find('.qx-slider').not(slider);
// Close any other open sliders.
sliders.each(function ()
{
$(this).hide();
var containerDiv = $(this).closest('.qx-container');
var outerBorder = containerDiv.find('.qx-container-border-outer');
var innerBorder = containerDiv.find('.qx-container-border-inner');
var backgroundDiv = containerDiv.find('.qx-container-border-background');
outerBorder.removeClass('qx-container-border-outer-selected');
outerBorder.removeClass('qx-container-border-outer-hover');
innerBorder.removeClass('qx-container-border-inner-selected');
inner.removeClass('qx-container-border-inner-hover');
backgroundDiv.removeClass('qx-container-border-background-selected');
background.removeClass('qx-container-border-background-hover');
});
// Toggle the slider.
slider.slideToggle(50, function ()
{
if (!container.data('hoverState'))
{
outer.removeClass('qx-container-border-outer-hover');
inner.removeClass('qx-container-border-inner-hover');
background.removeClass('qx-container-border-background-hover');
}
if (slider.is(':visible'))
{
outer.addClass('qx-container-border-outer-selected');
inner.addClass('qx-container-border-inner-selected');
background.addClass('qx-container-border-background-selected');
}
else
{
outer.removeClass('qx-container-border-outer-selected');
inner.removeClass('qx-container-border-inner-selected');
background.removeClass('qx-container-border-background-selected');
}
});
});
// Wire a change event to the check boxes. Stores the user's selection in the content element & displays the text of which check box is checked.
$('.qx-slider').find($('input[type="checkbox"]')).click(function (event)
{
event.stopPropagation();
handleCheckBoxToggle($(this));
});
// Wire a mouse enter event to the slider row so the background color changes to gray.
$('.qx-slider-row').mouseenter(function ()
{
$(this).find('td').addClass('qx-slider-cell-hover');
});
// Wire a mouse leave event to the slider row so the background color changes to white.
$('.qx-slider-row').mouseleave(function ()
{
$(this).find('td').removeClass('qx-slider-cell-hover');
});
// Wire a mouse click event to the slider row to toggle the check box's checked attribute.
$('.qx-slider-row').click(function ()
{
var checkBox = $(this).find('input[type="checkbox"]');
checkBox.attr('checked', !checkBox.is(':checked'));
handleCheckBoxToggle(checkBox);
});
// Handles the checked event for each check box.
function handleCheckBoxToggle(checkBox)
{
// Reference to the containing content div.
var content = checkBox.closest('.qx-container').find('.qx-content')
// Holds the checked values (data is associated with the content div).
var checkBoxData = content.data('checkBoxData');
// Reference to all the check boxes in the slider.
var checkBoxes = checkBox.closest('table').find('input[type="checkbox"]');
// Create an array of check box values (associated with the content div) if it doesn't exist.
if (checkBoxData == undefined)
{
checkBoxData = new Array();
checkBoxes.each(function ()
{
checkBoxData[$(this).attr('interest-level-description')] = 0;
});
}
// Store the checked values of each check box.
checkBoxes.each(function ()
{
checkBoxData[$(this).attr('interest-level-description')] = $(this).is(':checked') ? 1 : 0;
});
// Create a commo-delimited string from the checked values.
content.data('checkBoxData', checkBoxData);
var output = '';
for (var property in checkBoxData)
{
if (checkBoxData[property] == 1)
{
output += property + ", ";
}
}
// Remove the trailing comma.
if (output.match(",") != null)
{
output = output.substr(0, output.length - 2);
}
// Set the content text and class based on the checked values.
if (output == '')
{
content.text(content.attr('empty-message'));
content.addClass('qx-empty-content');
}
else
{
content.text(output);
content.removeClass('qx-empty-content');
}
}
http://jsfiddle.net/heray/1/
If you click the items you'll notice the dropdown menu appears behind subsequent dropdowns. I've added z-indexes and position relative to every element I can think of.
Just so you understand how to use z-index, never assign a z-index to something unless you want it to be displayed over top of another element. The best practice is not to define a z-index (especially not assigning a value of 0) until you need to. In your example, the class you have after clicking the button (the actual dropdown) should have a z-index of 1 or greater, and nothing else in your document should have any z-index definition. if you have an element with z-index of 1, and then you put another element in the same physical spot with a z-index of 2 -- the container with the higher z-index will overlap the one's with the lower.
Remove the z-indexes from the dropdowns. Also, what makes you think that setting a z-index of 0 on them will make things better?
Updated fiddle.
I have a problem with select input items.
What I want to achieve is dynamically remove already selected options from other select boxes so that I cannot select same item on multiple boxes. The problem is that elements are loaded in array via AJAX and I need to fill the select input with options after. This makes it so much harder.
In short - How can I make sure that if I select one item in select it will be deleted on all others, but when I select something else the previous item will be shown again and so on...
I have a fiddle as well, this is how far I got: http://jsfiddle.net/P3j4L/
You can try something like this
function updateSelects()
{
$('.selectContainer .select').each(
function (i, elem)
{
// Get the currently selected value of this select
var $selected = $(elem).find("option:selected");
// Temp element for keeping options
var $opts = $("<div>");
// Loop the elements array
for (i = 0; i < selectElements.length; i++)
{
var value = selectElements[i];
// Check if the value has been selected in any select element
if ($selected.val() == value || !$('.select option[value=' + value + ']:selected').length)
{
// if not create an option and append to temp element
$opts.append('<option value="' + value + '">' + value + '</option>');
}
}
// replace the html of select with new options
$(elem).html($opts.html());
if ($selected.length)
{
// set the selected value if there is one
$(elem).val($selected.val());
}
else
{
// new select element. So remove its selected option from all other selects
$('.selectContainer .select').not(this).find('option[value=' + $(elem).val() + ']').remove();
}
}
);
}
see a demo here : http://jsfiddle.net/P3j4L/1/
I am creating dynamic controls using jQuery by reading the query string parameter. These controls can be dragged to lay them in neat manner, after the drop event in drag/drop I would update the position and state of control through hidden field. It's just that my implementation now updates data at each drop which is not nice. I also did this through event but did not work well
Current Implementation
/*Create controls when only query string exists*/
if (config.params) {
//parse the query string json as a object
//ex:{type:"textbox",x:100,y:200,label:Name}
var params = $.parseJSON(config.params);
//for each control in json add it to document body
$(params).each(function (idx, obj) {
var id = new Date();//for generating dynamic id
id = id.getTime();
$("<span>").addClass(obj.css).load(function (ev) {
var a = this;
$(a).data("keys", {
key: $("label", a).text()**certainly label wont be found**,
value: $("input[name]:hidden", a)
});
}).
easydrag().ondrop(function (e, el) {
//easydrag drag drop plugin
var a = $(el),
b = a.data("keys"),
key = b.key,
value = b.value;
value.val(key + "$" + e.pageX + "$" + e.pageY);
}).
css({
position: "absolute",
top: obj.y,
left: obj.x
}).
append($("<label>", {
"for": id
}).
html(obj.label).
append(config.ELEMENTS(obj.type).attr("id", id))).
append($("<input>", {
"type": "hidden",
"name": "CONTROL$POSITION",
"id": "control_position_" + idx
})).
appendTo("form");
});
}
Question
As you see I attach data to span element on load. But since it is load event i won't have the inner label and hidden fields. I need to set the data on elementspan only once, since during drop event i will access the label and hidden field to update the current position of the control. I don't want to query the dom on each drop for the label and hidden field that's my purpose.
Also give me ideas on how I can redraw the controls after a postback?
Get rid of load, and temporary store a reference to the element. Execute the code after the element has fully finished creating:
var span = $("<span>");
span.addClass(obj.css).
easydrag(). //et cetera
....
appendTo("form");
//After the element has finished:
$(span).data("keys", {
key: $("label", span).text(),
value: $("input[name]:hidden", span)
});