Saving custom data to dom elements using jquery only once - javascript

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)
});

Related

How can I know if an user deleted an element in a contenteditable div?

I am using jQuery to detect if an # symbol has been inserted into a contenteditable div. When this is detected an ajax request is fired to the server to retrieve a list of users. A user would then click on the username (the function adduser will fire) and it will create a disabled textarea in the div.
Then I create an input dynamically with the id of the user selected as value (to notify them)
The problem I have is that if the users delete a textarea (a username) with the keyboard or even the mouse from the contenteditable div, How do I know which textarea it is and so remove too the input which has the user id as value?
My script:
function adduser(fname, lname, id, q)
{
var fname = fname;
var lname = lname;
var id = id;
var word = '#' + q;
var iid = 'id' + id;
var old=$("#contentbox").html();
var content=old.replace(word,"");
$("#contentbox").html(content);
var E="<textarea class='textareamention' name="+id+" id="+iid+" disabled >"+fname+"</textarea>";
$("#contentbox").append(E);
$('<input/>').attr({ type: 'text', name: 'mention[]', id: id, value: id}).appendTo('#post');
}
OK, here goes ...
A way ahead - maybe not the only one - is to use Mutation Observers, more particularly the Mutation-summary, library - to watch your #contentbox for changes. The Mutation-summary documentation even mentions this particular usage case - see last bullet point under What is it useful for?.
The advantage over event handling is that you don't need to predict every type of event that might cause deletion of a textarea.
A few warnings though :
Mutation Observers are currently supported only by the major browsers - Google Chrome, Firefox, Safari, Opera and IE11. "In the future it will be implemented in other browsers", we are told.
Mutation-summary hasn't been updated for some 3 years.
I have not actually used Mutation-summary in anger - I've only read the documentation.
Once a mutation is observed, there must be a number of ways to handle removal of a corresponding element.
One way, as I suggested in the comments above, would be to loop through all the inputs and remove any of them for which its corresponding textarea no longer exists.
However, I think there's a cleaner way.
First, install the Mutation-summary, library on the page (a <script src="..."> tag, like jQuery).
Then, modify adduser() to include a custom EventListener, removeRelatedElements, attached to your <textarea> elements :
function adduser(fname, lname, id, q) {
$('<textarea class="textareamention" name=' + id + ' id=id' + id + ' disabled>' + fname + '</textarea>')
.on('removeRelatedElements', function() {
$input.remove(); // $input is held in a closure
})
.appendTo($("#contentbox").html($("#contentbox").html().replace('#' + q, '')));
var $input = $('<input/>')
.attr({
'type': 'text',
'name': 'mention[]',
'id': id,
'value': id
})
.appendTo('#post');
}
Now, you should be able to use Mutation-summary to observe changes to #contentbox and, when a textArea is removed, trigger its custom removeRelatedElements event, causing the corresponding <input> element also to be removed.
var observer = new MutationSummary({
queries: [
{ element: '#contentbox' } // jQuery-like selector
],
callback: function(summaries) {
summaries[0].removed.forEach(function(removedEl) {
// Here, you should be able to write POJS or jQuery, or a mixture
if(removedEl.tagName.toUpperCase() === 'TEXTAREA') {
$(removedEl).trigger('removeRelatedElements').off('removeRelatedElements'); // remove the corresponding input element and detatch the custom handler to keep things clean
}
});
}
});
That's the gist of it anyway. You may well need to do some debugging.
EDIT:
OK, the above attempt doesn't work due to $("#contentbox").html().replace('#' + q, ''), which replaces the DOM hierarchy within #contentbox. In the process, our .on('removeRelatedElements', ...) event handlers get irrevocably blitzed and thereafter .trigger('removeRelatedElements') fails silently.
We have to revert to "plan A". At each mutation of .textareamention, you need to find and remove any input for which there is no corresponding textarea.
It's tempting to think you can simply work with summaries[0].removed as before, but no. The problem there is that summaries[0].removed includes references to the textareas that were removed during the replace('#' + q, '') process. Consequently all corresponding input elements get removed, including the one that was just added! The nett effect is that the expected input never appears.
Fortunately, the workaround is simple. We can completely ignore summaries and work with $("#contentbox textarea.textareamention") as a start point at each observed mutation. Don't worry if you can't grasp this point - it's not obvious. I discovered it by trial and error.
Here's the code :
jQuery(function($) {
var counter = 0; // integer, for associating each <textarea> with its <input>
$('#button').click(function() {
var q = " ";
$('<textarea class="textareamention ' + counter + '" disabled></textarea>') // include `counter` as a className
.text(counter) // for demo purposes
.appendTo($("#contentbox").html($("#contentbox").html().replace('#' + q, '')));
var $input = $('<input/>').attr({
'type': 'text',
'name': 'mention[]',
'value': counter // for demo purposes
})
.data('counter', counter) // bind the current counter value to this <input>
.appendTo('#post');
counter++;
});
var observer = new MutationSummary({
'queries': [
{ element: '.textareamention' }
],
'callback': function(summaries) {
// Remove all inputs for which there is no corresponding textarea.
var $textareas = $("#contentbox textarea.textareamention"); // all textareas
$("#post input").filter(function(index, element) {
return $textareas.filter('.' + $(element).data('counter')).length === 0;
}) // filter to find any input for which there is no corresponding textarea ...
.remove(); // ... and remove it/them.
}
});
});
DEMO. Note: in the fiddle, all the above code appears at the bottom of the javascript frame.
Try to register a callback function while deleting the textarea, like this:
<html>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
function del(id) {
if (event.keyCode == 8) {
alert(id);
$("#id"+id).remove();
}
}
function adduser(fname, lname, id, q) {
var fname = fname;
var lname = lname;
var id = id;
var word = '#' + q;
var iid = 'id' + id;
var old=$("#contentbox").html();
var content=old.replace(word,"");
$("#contentbox").html(content);
var E="<textarea onkeydown='del("+id+")' class='textareamention' name="+id+" id="+iid+" >"+fname+"</textarea>";
$("#contentbox").append(E);
$('<input/>').attr({ type: 'text', name: 'mention[]', id: id, value: id}).appendTo('#post');
}
</script>
<body>
<div id='contentbox'></div>
<button onclick="adduser(123, 123, 123, 123)">Click me</button>
</body>
</html>

Using data-attr to identify an active selection

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');

Change each list item background color conditionally JQuery Mobile

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;
}

Append additional key values to a different <div>

I've got radios attached to divs, and when the div is clicked the info on the widget updates to display the data associated to the div. I'm getting the data from the Yahoo API with JSON script. The appropriate data is appearing on my div called "Current", but when I click on "24-Hour" I get nothing.
Check out my script on this fiddle: http://jsfiddle.net/thetuneupguy/r2Bca/11/
$.each(data.query.results.quote, function (key, obj) {
var $lc = $('<tr/', {
'class': 'my-new-list'
}).appendTo('#blk-2 table');
$lc.append($('<td/>').text(obj.Name || "--"));
$lc.append($('<td/>').text(obj.PreviousClose || "--"));
});
It appears that I forgot to close out a tag, which I have caught and fixed...
var $lc = $('<tr/', {
should be...
var $lc = $('<tr/>', {

Jquery mobile - Click button to add to UL

i am trying to build an application that when the user enters text into a textbox on a jquery based mobile app and clicks save it adds it to the list on the screen
so by default i won't have a list, but as the user adds an item the list should be created or if the list already exists, the new item added as a new list item.
in terms of saving it i will work on that after, for the time being i just want to dynamically append to a ul in jqm on the screen
Can someone assist with code that may help with this. it is giving me an item added saying "item undefined" however numslist is my list and txtbox is the textbox so im not sure where i am going wrong
thanks
<script>
var $txtbox = $("#txtbox").val();
var count = 0;
$("#main").live("pagecreate", function(event) {
$("#numlist").listview({create: function(event, ui) {
$("#addBtn").bind("click", function(event, ui) {
var str = "<li><a href='#'>Item " + ($txtbox) + "</a></li>";
$("#numlist").append(str);
$("#numlist").listview("refresh");
});
$("#removeBtn").bind("click", function(event, ui) {
// if (--count < 0) {
// count = 0;
// return;
// }
$("#numlist").find("li").remove();
$("#numlist").listview("refresh");
});
}});
});
</script>
Well, you can use localstorage, that way you won't need to code extra functions that save/store data.
try this:
var $lst = $('#productList');
$("#btnID").on("click",function() {
var $txtBox = $("#txtBox");
var $li = $('<li/>').html($txtBox.val());
$lst.append($li).listview('refresh');
$txtBox.val("");
});
working fiddle here: http://jsfiddle.net/REthD/21/
If I understood your question correctly, something similar to the following should work for you:
$('input[type=button]').on('click', function() {
var ul = $('#ul_id').length > 0 ? $('#ul_id') : $('<ul />', { id: 'ul_id'}).appendTo('#parent');
$('<li />').text($('#textbox').val()).appendTo(ul);
});
The first line in the event will check if the element exists, if it does, it returns that, otherwise, creates a new and appends to the specified parent element. Then, it appends a to the with the text from the textbox.
jsFiddle example

Categories

Resources