I am looking for a recommendation on the best way to organised my code. I have a lot of jQuery event handle which are used for:
dropdown menus, tabs etc
form validation
$.ajax get requests for dynamic form <options>'s
$.ajax post requests for form submitting.
An MVC framework like backbonejs seems like overkill but my current code isn't maintainable and will continue to get worse unless i give it some kind of structure.
$('#detailsform').find('.field').on('click','.save',function(){
var input = $(this).siblings().find('input');
input.attr('type','hidden');
$(this).siblings().find('p').text(input.val());
$(this).text('Change').addClass('change').removeClass('save');
url = null; //query str
$.ajax({
type: "POST",
url: url,
data: data,
success: success,
dataType: dataType
});
});
//next event listener
//next event listener
//next event listener
//next event listener
that is one of many event listeners. Any suggestions on how to organise this?
I would use the following structure. Simply add more objects in the eventHandlers array for each element you need. This could even be done programatically.
(function() {
var Site = {
init: function() {
this.bindEventHandlers();
},
bindEventHandlers: function() {
for (var i=0; i<this.eventHandlers.length; i++) {
this.bindEvent(this.eventHandlers[i]);
}
},
bindEvent: function(e) {
e.$el.on(e.event, e.handler);
console.log('Bound ' + e.event + ' handler for', e.$el);
},
eventHandlers: [
{
$el: $('#element1'),
event: "click",
handler: function() { console.log('Clicked',$(this)) }
},
{
$el: $('#element2'),
event: "click",
handler: function() { console.log('Clicked',$(this)) }
}
]
};
Site.init();
})();
Fiddle here: http://jsfiddle.net/chrispickford/LQr2B/
Related
I know about event.preventDefault() and event.stopImmediatePropagation(). But it doesn't work for me. In my case I have such ajax call:
$('#templateConfirmDialog').on('show.bs.modal', function (event) {
$(this).find('.modal-yes').click(function(){
var form = form2js('search_form', '.', true, function (node) {}, false);
var requestData = JSON.stringify(form, replacer);
var $formErrors = $('.search_form').find('.alert-danger');
event.preventDefault();
event.stopImmediatePropagation();
$.ajax({
type: 'POST',
contentType : "application/json",
url: '/fraud/template/testCreate',
data: requestData,
dataType: 'json',
success: function (data) {
$formErrors.text('');
//if no errors just reload
if (data === undefined || data.length === 0) {
location.reload();
}
else {
//else bind error messages
data.forEach(function(error) {
$('#new-' + error.field + '-error').text(error.defaultMessage);
})
}
}
});
});
My problem is that the ajax call is prevented as much times as I made attempts to input data. If I entered invalid data once - ajax is called twice. If twice - 3 times. What may be a reason of such behavior?
Every time this event happens:
$('#templateConfirmDialog').on('show.bs.modal', function (event) {
You bind a new click event handler:
$(this).find('.modal-yes').click(function(){
So if you show.bs.modal twice, then you have two click event handlers both submitting the AJAX request. Instead, just bind the click event handler once to the target clickable element, instead of binding it every time the modal is displayed.
Replace this:
$('#templateConfirmDialog').on('show.bs.modal', function (event) {
$(this).find('.modal-yes').click(function(){
//...
});
});
With this:
$('#templateConfirmDialog').find('.modal-yes').click(function(){
//...
});
Or, if that element is dynamically added to the DOM, this:
$(document).on('click', '#templateConfirmDialog .modal-yes', function(){
//...
});
That way there's just a single click event handler created when the page loads, rather than adding a new handler every time you display the modal.
So I'm getting data from a back-end through an AJAX GET method, and presenting it in a list(below) in html. I tried to put the button tag in there and I get the buttons on the list but I'm not sure how to use the delegate and others to make it work.
So how can I put independent buttons that send the users to a details page about the cafeteria in that list? (This is just a personal project)
$(function(){
var $cafeterias = $('#cafeterias');
var $Name = $('#CName');
var $Location = $('#CLocation');
function DispCafeteria(cafeteria) {
$cafeterias.append('<li> Name: '+cafeteria.Name+'Location: '+cafeteria.Location+'<button id="Details">Details</button>'+'</li>');
}
$.ajax({
type: 'GET',
url: 'some url',
success: function (cafeterias) {
$.each (cafeterias, function (i, cafeteria){
DispCafeteria(cafeteria);
});
},
error: function() {
alert('Error while loading cafeterias');
}
});
});
A few nit pick:
Don't name functions with a leading capital letter unless your planning to instantiate it (constructor function). It confuses people since this is the common de facto standard.
Avoid nested callback logic. Use a promise interface instead. (jQuery has one).
Don't construct interactive DOM elements with HTML strings. It makes it difficult to attach events to it. An exception is using event delegation which in your example is a better more performant way to do that.
You should break your problem space down. Separate concerns into smaller chunks.
Fetch AJAX Data:
function fetchData() {
return $.ajax({
type: 'GET',
url: 'some url'
});
}
Handle errors:
function handleErrors(err) {
alert('Error while loading cafeterias');
}
Construct the DOM:
function cafeteriaToString(cafeteria) {
return 'Name: ' + cafeteria.name +
' Location: ' + cafeteria.location;
}
function constructDataTable(cafeterias) {
$.each(cafeterias, function (i, cafeteria) {
var $button = $('<button/>')
.text('Details')
.data('details-id', i);
$('<li/>')
.text(cafeteriaToString(cafeteria))
.append($button);
});
}
Attach a delegated event:
function handleButtonClicks(e) {
var detailsId = $(this).data('details-id');
// Do something with detailsId
}
Putting it all together:
function init() {
fetchData()
.then(constructDataTable)
.fail(handleErrors);
$('ul').on('click', 'li>button', handleButtonClicks);
}
I have php page "Home.php", that present user posts(using ajax).
This is how I get the posts:
<script type="text/javascript">
function loadmore()
{
var val = document.getElementById("result_no").value;
var userval = document.getElementById("user_id").value;
$.ajax({
type: 'post',
url: 'fetch.php',
data: {
getresult:val,
getuserid:userval
},
success: function (response) {
var content = document.getElementById("result_para");
content.innerHTML = content.innerHTML+response;
// We increase the value by 2 because we limit the results by 2
document.getElementById("result_no").value = Number(val)+10;
}
});
}
</script>
<div id="content">
<div id="result_para">
</div>
</div>
In every post, there is a like button(which also uses ajax). This is how I save the likes:
<script type="text/javascript">
function likethis(likepostid)
{
$.ajax({
type: 'post',
url: 'fetchlikes.php',
data: {
getpostid:likepostid
},
success: function (response) {
}
});
}
</script>
Before I used ajax to present posts, all worked well. But now when I press the like button, it DOES save the like, BUT the javascript/jquery doesn't work. I tried to make alert when I pressed the LIKE button, but it didn't work.
This is the index.js code(the javascript). It add +1 likes, when the user press the button:
$('.btn-counter_likecount').on('click', function(event, count) {
event.preventDefault();
//alert("hello");
var $this = $(this),
count = $this.attr('data-count'),
active = $this.hasClass('active'),
multiple = $this.hasClass('multiple-count_likecount');
$.fn.noop = $.noop;
$this.attr('data-count', ! active || multiple ? ++count : --count )[multiple ? 'noop' : 'toggleClass']('active');
});
EDIT fetchlikes.php:
<?php
mysql_connect('localhost','root','');
mysql_select_db('blabla');
$postid=$_POST['getpostid'];
mysql_query("UPDATE user_post SET likes_count=likes_count+1 WHERE post_id='$postid'");
?>
Because your posts are being loaded dynamically, the javascript where you bind the event is running before the posts are actually loaded, thus the buttons don't exist when you try to bind the event. You can use delegated events in jQuery to handle this.
Your previous code
$('.btn-counter_likecount').on('click', function(event, count) {
....
});
New Code
$('#result-para').on('click','.btn-counter_likecount',function(event, count) {
....
}
This way the event will actually be bound to a parent element that already exists when jQuery's ready() function runs. This way, the event handler checks for matching elements when the event is fired rather than when the event is bound.
For further reading, look into jQuery's delegated events
So I'm just getting started with event delegation and I'm still fairly confused by it but here goes:
I have a button which adds a rating in ajax, once clicked again I'd like it to remove the rating, here's the code with annotations (and some parts removed to make it look more clear).
$(document).on("click", '.add_rating', function() {
l.start();
var input = $(this).prev().children('.my_rating');
var score = input.val();
var what_do = input.attr('action_type');
var cur_average = $('.current_average').val();
var data = {};
data.score = score;
data.media_id = <?php echo $title_data->media_id; ?>;
data.what_do = what_do;
$.ajax({
dataType: "json",
type: 'post',
url: 'jquery/actions/add_remove_rating',
data: data,
success: function(data) {
if (data.comm === 'success') {
//do some other stuff there, irrelevant
$('.ladda-button').removeClass('btn-primary');
$('.ladda-button').removeClass('btn-sm');
$('.ladda-button').addClass('btn-danger btn-xs');
$('.ladda-label').html('Remove');
$('.ladda-button').addClass('remove_rating'); <-- add the remove rating class I want to call if the button is clicked again
input.attr('action_type', 'remove_rating');
l.stop();
}
}
});
$('.remove_rating').on('click', function() { <-- this doesn't work, why?
alert('remove was clicked');
});
});
I can't seem to trigger this:
$('.remove_rating').on('click', function() { <-- this doesn't work, why?
alert('remove was clicked');
});
Any help appreciated!
Edit: on a side note, I don't actually need this to work as php figures out if we're removing or adding a score based on the action_type attribute. I just wanted to find out why it's not triggering.
change your code to:
$(document).on("click", '.add_rating', function() {
l.start();
var input = $(this).prev().children('.my_rating');
var score = input.val();
var what_do = input.attr('action_type');
var cur_average = $('.current_average').val();
var data = {};
data.score = score;
data.media_id = <?php echo $title_data->media_id; ?>;
data.what_do = what_do;
$.ajax({
dataType: "json",
type: 'post',
url: 'jquery/actions/add_remove_rating',
data: data,
success: function(data) {
if (data.comm === 'success') {
//do some other stuff there, irrelevant
$('.ladda-button').removeClass('btn-primary');
$('.ladda-button').removeClass('btn-sm');
$('.ladda-button').addClass('btn-danger btn-xs');
$('.ladda-label').html('Remove');
$('.ladda-button').addClass('remove_rating'); <-- add the remove rating class I want to call if the button is clicked again
input.attr('action_type', 'remove_rating');
l.stop();
$('.remove_rating').on('click', function() { <-- this doesn't work, why?
alert('remove was clicked');
});
}
}
});
});
EXPLANATION:
first have a look here: Understanding Event Delegation.
event delegation is used when you need to create event handlers for elements that do not exist yet. you add a .remove_rating class to elements dynamically, however you are trying to attach a handler to elements with the above mentioned class before you even attach it.
you are attaching the class when the asynchronous ajax call returns, in the success function, however your event handler block is being processed right after you send the ajax, and not after the ajax returns (ajax is async rememeber?). therefore, you need to wait until the ajax returns and the elements are created, and only then attach the handler to them.
alternatively, using event delegation, you can attach the handler to the document, like you did in the following line:
$(document).on("click", '.add_rating', function() {
it means, that you attach the handler to the document, and whenever any element ON the document is clicked, if that element has the class '.add_rating' then execute the handler.
therefore, you may attach another handler to the document to monitor for clicks on elements with the .remove_rating class as follows:
$(document).on("click", '.remove_rating', function() {
this is called event delegation, because you delegate the event to a parent element.
Because class was added after click event initialised. You need to use live event handlers, like this:
$( document ).on('click', '.remove_rating', function() {
In this case .remove_rating click handler will work on dynamically created elements and on class name changes.
I have the following code for handling on click event of a button:
$(document).on("click", '.submit_review_button', function(event, ui) {
var place = $.data(this, "object");
var ttext = $("#review_text").val();
var review = new Object();
review.business_place_id = place._id;
review.review = ttext;
review.user_id = user._id;
// var review = {business_place_id:place.id, review: ttext, user_id: user.id}
$.ajax({
url: site_url + '/reviews/',
type:'POST',
data: review,
success: function(data) {
$.mobile.changePage("show_reviews_page", {
allowSamePageTransition: true,
transition: 'none',
reloadPage: true
});
// initShowReviewsPage();
},
error:function(data) {
alert(1);
}
});
});
I also have this code in document-ready:
$("#show_reviews_page").on('pageinit', function() {
initShowReviewsPage();
});
I know that the pageInit binding works, because if I go to #show_reviews_page using it works.
But when clicking on the .submit_review_button button, the on click event fires, the page changes but the init doent fire and the page is not valid.
Any idea why it doesnt work?
"pageinit" event is fired only once when the page loads in the DOM for the first time.
If you want fire a function everytime you go to a page, use "pageshow" or "pagebeforeshow" events.