Jump to content
Larry Ullman's Book Forums

Recommended Posts

When I am developing an application for a client, I run into situations where I have to engineer what seems to be the best idea that I can come up with at the time. Of course that is the whole game right?

 

I work alone and have no other programmer buddies to bounce a code idea off of, so I wing it and hope for the best. 

 

Recently an application specification calls for a "row of buttons" across the top of an admin page that also has tabbed "main navigation" links to non-admin application stuff. The idea is to make it "so simple a caveman could do it" by seeing the desired functionality at a glance and clicking a button to refresh the page via ajax to get the desired content. 

 

This is where the question comes in. I started with a jquery button click handler for each button that contained the associated ajax call. Ten buttons meant 10 separate click functions with an anonymous ajax function call. It seemed like way too much code to accomplish the mission with a lot of places for things to go wrong. It also did not at all adhere to the DRY axiom one bit. 

 

So I am trying something to streamline the process.

 

Here is the HTML for the buttons:

//All button ids will correspond to the associated action called in the jquery click handler
echo'<div class="admin_buttons_div">
		<button id="get_user_table" class="button">View Users</button>
		<button id="get_dmd_info" class="button">Get DMD Report</button>
		<button id="get_dmd_entry" class="button">DMD Entry</button>
		<button id="button_4" class="button">button_4</button>
		<button id="button_5" class="button">button_5</button>
		<button id="button_6" class="button">button_6</button>
	 </div>';

//This is our place holder div to contain our ajax responses	 
echo'<div class="placeholder_div"></div>';

Here is the jquery code:

//admin_dashboard.js
$(document).ready(function(){
	
	$('.button').on('click',function(evt){
		
		//Get a reference to the button
		var button = $(this);
		
		//Get the clicked button's default text
		var initial_text =  $(this).text();
		
		//Get the button's id which is also the php $_POST['action'] variable in action.php
		var action = $(this).attr('id');
		
		//Button was clicked...give the user some visual feedback
		processing(button);
		
        //Set the action variable for the called php script - action.php
        var data = 'action=' + action;
      
	//Send the ajax data request to the action.php script
        $.post('modules/action.php', data, function(ajax_response){
			//ajax response returns whatever the called script is written to return
			$('.placeholder_div').html(ajax_response);
			//Set the button text back to it's default value 
			$(button).text(initial_text);
		});//$.post
	
    });//End click function
	
	
    //Give the user some visual feedback after a button click
    function processing(button){
      $(button).text('Processing...');
    }
	
    //Reset the button text to the default initial text 
    function done_processing(initial_text){
       $(button).text(initial_text);
    }

});//ready

The data sent in the ajax function is the button's id and is received  by action.php as a post variable that is used in a switch to select either a php function or a php script that returns a response and populates the the place holder div with whatever the clicked button was supposed to do.

Here is the scaled down relevant php code:

include('../includes/mysqli_connect.inc.php');

$action = isset($_POST['action']) ? $_POST['action'] : NULL;
	
// Determine what action to take based on $action sent by js click function
switch ($action) {

 	case 'get_user_table':
			echo display_users();
		break;

	case 'get_dmd_info':
			display_dmd_info();
		break;

	case 'get_dmd_entry':
			display_dmd_form();
		break;			
 
    // Default is to include the main page.
    default:
        $page = 'main.inc.php';
        $page_title = 'Site Home Page';
        break;
}//end switch		
?>

I actually have many more cases to the switch because it is acting as a controller for all of my ajax function calls.

 

The code works very well and seems real easy to add buttons, cases and functions to achieve the desired functionality. The user clicks the button, gets visual feedback via button text changing to "Processing..." then the content is updated and the button text reverts back to the original value. Seems like a good idea to me.

 

The main application framework is built on the concepts presented in Chapter 2 "Modularizing a Web Site" with a MVC flavor tossed in all coded in a procedural fashion.

 

So...Is one programmer's clever idea another programmer's spaghetti code? Is this a bad idea? Thanks for any suggestions or opinions. 

  • Upvote 1
Link to comment
Share on other sites

I found that if a user started clicking buttons like a madman, the system would fail. My solution was to disable the other buttons until the ajax response was completed, immediately after that I re-enable the buttons. Now it is impossible to click too fast.

$('.button').on('click',function(evt){
		
		//disable the other buttons temporarily
		$('.button').prop( "disabled", true );
		
		//Get a reference to the clicked button
		var button = $(this);
		
		//Get the clicked button's default text
		var initial_text =  $(this).text();
		
		//Get the button's id which is also the php $_POST['action'] variable in action.php
		var action = $(this).attr('id');
		
		//Button was clicked...give the user some visual feedback
		processing(button);
		
        //Set the action variable for the called php script - action.php
        var data = 'action=' + action;
      
		//Send the ajax data request to the action.php script
        $.post('modules/action.php', data, function(ajax_response){
			//ajax response returns whatever the called script is written to return
			$('.placeholder_div').html(ajax_response);
			//re-enable the other buttons now
			$('.button').prop( "disabled", false );
			//Set the button text back to it's default value 
			$(button).text(initial_text);
		});//$.post
	
    });//End click function
  • Upvote 1
Link to comment
Share on other sites

another_noob, I think you're on the right track with your code and implementation.

 

Just a few things I would recommend:

  1. Nowadays, I almost always attach my click handler to the document, as that will catch everything in the entire document via evt.target (or window.event.srcElement in old IE). The one caveat to this approach is that on Apple iDevices, only clicks on a links or form inputs will bubble up to the document. Kind of annoying, but not too hard to handle as long as you go into your code expecting that. Also, because all clicks will bubble up to the document, I generally check the class on an element to ensure that I have what I want.
  2. When I have a bunch of similar buttons and I need to be able to differentiate them for specific actions, I will often use data attributes so that I don't have to rely on the id attribute to distinguish something. (In other words, I can better use the ID attribute for its intended purpose, and not tie it up with a bunch of JS functionality.) For example, I might add data-id="value" to each element where value is some simple string that you send to the PHP side for processing.
  3. Instead of actually disabling the buttons, you can easily create a flag that, when set to true, does not allow for processing. It's easier to code, and more true to what I think you want to achieve.

 

Rolling all of the above together, I might implement what you're doing as follows:

var btnProcessing; //Must be available outside the scope of the event listener to always retain its value.

addEvent('click', document, function (evt, target) { //target is the button clicked
  
  if (hasClass(target, 'button') && !btnProcessing) { //If class of "button" and not currently processing another request
    
    btnProcessing = true; //Flag to denote processing
    
    post({
      
      url: 'modules/action.php',
      
      params: {
        
        action: attr(target, 'data-id') //Value of the data-id attribute attached to the button
        
      },
      
      success: function (html) { //HTML returned from PHP script
        
        qS('.placeholder_div').innerHTML = html; //qS = document.querySelector alias
        
        btnProcessing = false; //Reset flag to allow for processing
        
      }
      
    });
    
  }
  
});
 
Please note that the above code relies on my custom-built JS library, which is jQuery-esque, but much lighter and more efficient.
Nevertheless, I think the parallels to jQuery are apparent enough that you can easily convert the code over.
Link to comment
Share on other sites

Thank you HartleySan for the advice! I had to Google the data-id attribute as I have never, until now, had a real world application for it...or I didn't know how to use it correctly so I didn't. Good stuff. I will refactor my code and post the results.

 

I probably should use straight javascript, but I guess I just got lazy and comfortable with jquery.

Link to comment
Share on other sites

I spent some time with your suggestions and I am half way there.

 

Here is the HTML:

<script src="js/admin_dashboard.js"></script>
<?php
include('includes/mysqli_connect.inc.php');

//All button ids will correspond to the associated action called in the jquery click handler
echo'<div class="admin_buttons_div">
		<button id="get_user_table" data-action="get_user_table"  class="button">View Users</button>
		<button id="get_dmd_info" data-action="get_dmd_info" class="button">Get DMD Report</button>
		<button id="get_dmd_entry" data-action="get_dmd_entry" class="button">DMD Entry</button>
		<button id="button_4" class="button">button_4</button>
		<button id="button_5" class="button">button_5</button>
		<button id="button_6" class="button">button_6</button>
	 </div>';

//This is our place holder div to contain our ajax responses	 
echo'<div class="placeholder_div"></div>';
		



And here is the refactored jquery:

//admin_dashboard.js

$(document).ready(function(){
	
	$('.button').on('click',function(evt){
		
		//disable the other buttons temporarily
		$('.button').prop( "disabled", true );
		
		//Get a reference to the clicked button
		var button = this;
		
		//Get the data attribute from the clicked button
		var action = button.getAttribute("data-action");
		
		//Set the ajax data and the action variable for the called php script - action.php
                var data = 'action=' + action;
						
		//Get the clicked button's default text
		var initial_text =  $(this).text();
						
		//Button was clicked...give the user some visual feedback
		processing(button);
		      
		//Send the ajax data request to the action.php script
        $.post('modules/action.php', data, function(ajax_response){
			//ajax response returns whatever the called script is written to return
			$('.placeholder_div').html(ajax_response);
			//re-enable the other buttons now
			$('.button').prop( "disabled", false );
			//Set the button text back to it's default value 
			$(button).text(initial_text);
		});//$.post
	
    });//End click function	
	
	//Give the user some visual feedback after a button click
	function processing(button){
		$(button).text('Processing...');
	}
	
	//Reset the button text to the default static text set in the calling script
	function done_processing(initial_text){
		$(button).text(initial_text);
	}
	
	

});//ready



I couldn't get the flag to work correctly for me, I could still click like a madman on the buttons and cause too much processing at once. I will revisit that when I have more time. For now, it works and I have to move on.

 

I do really like the data-key="value" attribute concept. It gives us more flexibility when integrating ajax calls into the fold. I played around with it and see many places in my other code where it would have helped big-time. For instance in a case where I need to get the value from, say for example, a <li> which was generated from a mySQL fetch and is made clickable with jquery:

<li id="part-num" class="part-num_422">122-12345AZS</li>

And then I have to break down the class in jquery to extract the id of the part number, instead I can use this:

<li id="part-num" class="whatever" data-part-id="'. $php_variable .'" >122-12345AZS</li>

Now it is a snap to grab the id and process as needed.

 

Or is this a bad practice to set the data-key="value" dynamically from a database with PHP?

Link to comment
Share on other sites

It's not bad practice at all to dynamically set data attributes with PHP. By all means, you should if the situation calls for it.

 

Also, like I recommended in #1 in my previous post, I think it would help you if you attached the click handler to the document object for this page.

 

Also, what code did you try to use to get the flag to work?

Thanks.

Link to comment
Share on other sites

Once again HartleySan, Thank you for you time! I couldn't just leave it the way it was knowing that you suggested a better way. With some sleep, time to think, research things a little, and comparing to your javascript example, I think I have it! It works perfectly. I monitored things closely each step in firebug console and confirmed values with console.log().  I clicked away like a madman and no problems. The flag variable allowed me to eliminate the functions and function calls previously used to toggle the text values...sweet!

 

Here is my latest code:

$(document).ready(function(){

	//Flag variable to track state of clicked button
	var btnProcessing;	
	
	//Event attached to the document object
	$(document).click(function(e){
	
	  //target is the button clicked. e is the event	
	  if ($(e.target).is('.button')){
		
		//Get the clicked button's default text
		var initial_text =  $(e.target).text();
				
		//Get the html data attribute from the clicked button
		var action = e.target.getAttribute("data-action");
		
		//Set up the ajax data variable to select the correct switch case in action.php 		
		var data = 'action=' + action;
		
            //If class of "button" and not currently processing another request
	    if ($(e.target).hasClass( "button" ) && !btnProcessing) { 
			
			//Flag to denote processing
			btnProcessing = true; 
			
			//Visual feedback on button
			$(e.target).text('Processing...');
			
			//Process the request
			$.post('modules/action.php', data, function(ajax_response){
				//ajax response returns whatever the called script is written to return
				$('.placeholder_div').html(ajax_response);
				//Got want we wanted...free up the buttons
				btnProcessing = false;
				//re-set the button text back to the original value
				$(e.target).text(initial_text);
			});//$.post

		
		}//if ($(e.target).hasClass
		
	  }//if ($(e.target).is('.button')
	  
	})//$(document).click(function(e){

});//ready

Now all I do is always use .button as the class name for a button (a no brainer) and use html data-key="value" to feed to the PHP ajax controller script and I never have to reinvent the wheel to process my ajax calls. I think I should be able modify this code to work more generically too. I am thinking about when I attach a click event to a list item.

 

Thanks for your help! This is a cool piece of code! If you see anything that should be changed let me know.

Link to comment
Share on other sites

I am glad I pursued this. I learned several big new things which will help me get to that ever elusive "next level", it's like a never ending ladder. 

 

Attaching the click event to the Document Object solved another issue for me. I was having trouble with dynamically created elements and attaching click events with one script. Sometimes I had to create another javascript file to attach click handlers to the dynamically created elements. I would then have to include that script in my PHP function or PHP script. It was getting confusing to keep things straight. This solved all those problems because the click handler works great on dynamically created content elements.

 

The data-key="value" attribute is clearly explained in a HTML5 book I have, but the author was explaining how to set dimensions of pop-ups with it, so I wasn't interested in pop up windows and skipped over those few (now very important) pages. I gotta learn to pay better attention when reading.

 

Thanks for the nudge in the right direction!

Link to comment
Share on other sites

I added a needed feature to the click handler. I need to select the "action" for the controller switch case, but some times I do need a database record id from the from the button click. So I came up with a generic data attribute I will call an "extra".  In my HTML here is an example how I will do it:

<button id="get_dmd_form" class="button" data-action="get_dmd_pdf" data-extra="'.$dmd_row['repair_id'].'">Get DMD Form</>

Now if I keep this generic attitude in my handler like this:

if(extra != null){			
        var data = 'action=' + action + '&extra=' + extra;
}else{
	var data = 'action=' + action; 
}	

Then in the PHP controller script:

	case 'get_dmd_pdf':
			$repair_id = isset($_POST['extra']) ? $_POST['extra'] : NULL;
			display_dmd_pdf($repair_id);
		break;					

In the controller is where I actually break from generic to specific by naming the variable to something meaningful. In this case the function sets up an fpdf document and outputs it for download. The handler in it's entirety now looks like this:

$(document).ready(function(repair_id){

	//Flag variable to track state of clicked button
	var btnProcessing;	
	
	//Event attached to the document object
	$(document).click(function(e){
	
	  //target is the button clicked. e is the event	
	  if ($(e.target).is('.button')){
		
		//Get the clicked button's default text
		var initial_text =  $(e.target).text();
				
		//Get the html data attribute from the clicked button to select the php switch case
		var action = e.target.getAttribute("data-action");
		
		//Set up an additional data attribute here
		var extra = e.target.getAttribute("data-extra");
		
		//Set up the ajax data variable to select the correct switch case in action.php, name and append the extra data to the action variable if needed	
		if(extra != null){			
			var data = 'action=' + action + '&extra=' + extra;
		}else{
			var data = 'action=' + action; 
		}	
		
		//If class of "button" and not currently processing another request
	    if ($(e.target).hasClass( "button" ) && !btnProcessing) { 
			
			//Flag to denote processing
			btnProcessing = true; 
			
			//Visual feedback on button
			$(e.target).text('Processing...');
			
			//Process the request
			$.post('modules/action.php', data, function(ajax_response){
				//ajax response returns whatever the called script is written to return
				$('.placeholder_div').html(ajax_response);
				//Got want we wanted...free up the buttons
				btnProcessing = false;
				//re-set the button text back to the original value
				$(e.target).text(initial_text);
			});//$.post
		
		}//if ($(e.target).hasClass
		
	  }//if ($(e.target).is('.button')
	  
	})//$(document).click(function(e)

});//ready

Works great.

  • Upvote 1
Link to comment
Share on other sites

  • 4 months later...

I have refactored this jQuery click handler several times over. It has became the basis, and heart & soul, of several practice "single page applications". Now it looks like this:

$(document).ready(function () {
	
    //Flag variable to track state of clicked element
    var btnProcessing;	
    
    //Event attached to the document object
    $(document).click(function(e){
    
    //target is the element clicked. e is the event	
    if ($(e.target).is('.js_button') || $(e.target).is('.js_list_item')){
    
    //Get the id of clicked element to use for the switch cases
    var element_id = e.target.getAttribute("id");
    
    //Do not submit a form with a <button> click
    e.preventDefault();
			
    //Keep event bubbling from duplicating events
    e.stopImmediatePropagation();

    //Get the clicked button's default text
    var initial_text =  $(e.target).text();
    //Get the html5 data attribute from the clicked element to select the php switch case in ajax_action.php
    var ajax_action = e.target.getAttribute("data-ajax_action");
    //Get the html5 data attribute from the clicked element to use as data for PHP/MySQL processing
    var ajax_data = e.target.getAttribute("data-ajax_data");
    
    //If form submission, collect form data with serialize()
    if(element_id == 'js_submit'){
    ajax_data = $('.js_form').serialize();
    }		

    //If clicked element is of class ".js_button" and not currently processing another request, or is of class ".js_list_item"
    if (($(e.target).hasClass( "js_button" ) && !btnProcessing) || ($(e.target).is('.js_list_item'))) { 
        //Flag to denote processing
        btnProcessing = true; 
        //Set visual feedback on button or list item
        $(e.target).text('Processing...');

        //Process the request
        $.post('ajax_action.php', {ajax_data : ajax_data, ajax_action : ajax_action}, function(ajax_response){
            //ajax response returns whatever the called script is written to return
            $('.ajax_div').html(ajax_response);
            //Got the response...free up the buttons
            btnProcessing = false;
            //re-set the element text back to the original value
            $(e.target).text(initial_text);
        });
       }
     }
   })
});



I refactored it to follow some very basic conventions. Buttons will all be of class js_button and use the following example for all HTML5 data attributes:

<buttons class="js_button" data-ajax_action="someFunction" data-ajax_data="someData">Click Me</button>

Forms follow a simple convention:

<form class="js_form">
    //form fields as usual
    <button class="js_button" id="js_submit" data-ajax_action="someAction">Submit</button>
</form>

The ajax_action attribute will relate to a case of the same name in the ajax_action.php script which will call a function of the same name:

case 'someFunction':
    someFunction($ajax_data);
    break;	

I use a mysqli_connect.php as a "model" type of thinking. In this script I have  database interaction functions:

function someFunction($ajax_data){
   //Do some database CRUD stuff
}

Of course the $ajax_data parameter could (and should) be named something that makes sense to the query, like perhaps $customer_id or $customer_data. That would make it easier to see at a glance the function's purpose.

 

I can submit form data and I can pass a php array as JSON with the data-ajax_data attribute. This has given me a lot of flexibility  for SPA development.

 

Following the required conventions of the click handler and with the required simple file structure, I have set up a simple to use MVC SPA where the index.php is the view, the ajax_action.php is controller and the mysqli_connect is the model. The click_handler.js file is the router...sort of.

 

it all may appear convoluted...as most code does, it is quite simple to use.

 

It is a lot of fun to play with and the possibilities are endless.

Link to comment
Share on other sites

 Share

×
×
  • Create New...