Tutorial: Creating an Ajax based form using Zend Framework and YUI

Many of you would already know that Dojo is JavaScript framework of the choice for Zend Framework. However, Zend framework itself is very loosely coupled that you can use whatever JavaScript framework you are comfortable in. As part of my previous project we were already using YUI, so it was the best idea that while Zend Framework was our choice for our next project, we should stick to the JavaScript framework in which we already have developed our expertise i.e. YUI. As part of developing an Ajax based form, it takes a bit of time to take your head around but it works exactly as the JavaScript should work with PHP i.e. unobtrusive way. The deal is simple, the whole point of using a MVC architecture is that, you have different layers of presentation, controlling and business logic, that makes your code easier to maintain and develop upon, same should be the case of JavaScript you will be using in your application. Just a note that I am using Zend Framework 1.10 for this mini-tutorial.

Let’s get to work!

Say you have a form in your forms directory which extends Zend_Form class:

<?php class Forms_Product_Values extends Zend_Form { public function init() { // Set the method for the display form to POST $this->setMethod('post'); $this->addElement('text','my_timestamp',array( 'filters'=>array('StringTrim'), 'required'=>true, 'label'=>'Time Stamp' )); $this->addElement('text','my_value',array( 'filters'=>array('StringTrim'), 'required'=>true, 'label'=>'Currency Value' )); // Add the submit button $this->addElement('button', 'values', array( 'ignore' => true, 'label' => 'Modify', )); } } ?>
In here, note the last element, button, normally if it is a non Ajax based form, the element type will be the submit but as we do not want the form to submit itself straight away, we would just like to create a button element and give it a name, in this case ‘values’, same as controller name, I will explain this later that why I have used this specific name in our tutorial.
Next you will create a corresponding controller action obviously to handle whatever you want to do with your form input:
public function valuesAction(){ $form = new Forms_Product_Values(); $request = $this->getRequest(); // Check to see if this action has been POST'ed to. if ($this->getRequest()->isPost()) { // Now check to see if the form submitted exists, and // if the values passed in are valid for this form. if ($form->isValid($request->getPost())) { //do whatever you want to do } } $this->view->form = $form; // disable layouts for this action: $this->_helper->layout->disableLayout();
}
and not values.phtml which will act as a view for our controller action:
<?=$this->form?>
OK, up to here it was simple, straight form without submit button in the form, now we need YUI to act here. You can either create a separate JS file (always recommended) or just paste your JavaScript code in the values.phtml (your view file) which is not recommended however, I am doing that just for demonstration purpose of this tutorial!
Here is you updated values.phtml:

<!-- Combo-handled YUI JS files: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.8.1/build/yahoo-dom-event/yahoo-dom-event.js&2.8.1/build/connection/connection-min.js&2.8.1/build/selector/selector-min.js"></script>
<script type="text/javascript">
function fnCallback(e) {
sUrl = e.currentTarget.id; //controller name is the same as button name;
var nodes = YAHOO.util.Selector.query('input');
//prepare the data in query string format var data = ""; for (var i = 0; i< nodes.length; i++) { data += nodes[i].name+"="+nodes[i].value; if(i != nodes.length-1){ data += "&"; } }
var div = document.getElementById("responseDiv");
var handleSuccess = function(o) { //upon successful response we embed the response text into responseDiv on our HTML page div.innerHTML = o.responseText; YAHOO.util.Event.addListener("submitButton", "click", fnCallback); }; var handleFailure = function(o){ //if the request if failed, we need to log the event if(o.responseText !== undefined){ div.innerHTML = "Failed request to the server. Please try again."; } }; var handleEvent = { start:function(eventType, args){ }}; var callback = { //on start call back calls the handle event which starts showing ajax loader image customevents:{ onStart:handleEvent.start }, success:handleSuccess, //parameter to define which function to use on success failure:handleFailure, //parameter to define which function to use on failure timeout:1500 //timeout is 1500ms, can be extended from here }; var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, data); } function init(){ YAHOO.util.Event.addListener("values", "click", fnCallback); } YAHOO.util.Event.onDOMReady(init);
<div id="responseDiv">
<?=$this->form?>
</div>

See, no rocket science, however, couple of things to note, I am using a different approach here to remember controller name, I have assigned the same name to the button as the controller i.e. ‘values’, on click event the listener fires fnCallback function and then I read what is the id of the button, hence I know which controller is this and that is all I need as sUrl (the URL which I need to call, you may also need to concatinate “../../”+e.currentTarget.id),  another approach you can use is to echo the controller name in a hidden span or div in the view and then just read it from the JavaScript on click or if you can come up with any other approach, kindly post in comments of this tutorial for others to use.

Then is the YAHOO.util.Selector.query(‘input’), the beauty of YUI become handy here, as in this form I only have input elements I want to read, however, if you want to read say for example all the select and textarea elements, it is simple, just use this YAHOO.util.Selector.query(‘input’, ‘select’, ‘textarea’), now you know how to harvest your inputs from your form, you pull that on into the data variable and make the normal Ajax request through connection manager utility of YUI. The only bit remains here is, because your controller action is same, when you will send a request to it, your whole form will regenerate itself (which is intentional as in the controller I am actually making a database call and I want to show that whatever user has modified is actually in the database now) so you will loose the listener which you attached earlier to the button, you have to add it again on receiving the response, however, in this example, I have hard-coded it, you may want to send it dynamically with function call so that your function become totally dynamic and you could use same with all of your forms, I leave that upto you!

Happy Coding!

YUI Button: Mimicking the native Select dropdown to avoid IE width problem

This is the 5th installment in YUI series. This installment uses YUI button widget and YUI keylistener utility. Although this post and example mimics the default SELECT element behaviour and look but you should also consider the JavaScript execution overhead this will cause.

Microsoft in all it’s glory continued to develop it’s own web standards, avoiding to support any open industry standards but then as technology progressed, word spread, people got knowledge and resources, open-source alternatives started to come into the market which now threatens the very existence of Microsoft itself. Yes, I am talking about the browsers at this moment, what Firefox and Google Chrome has done against a browser which comes pre-installed with an operating system which most of the people around the world use, yes the mighty IE (Internet Explorer).

As being a web-developer, you most probably would know the pain of working with IE. Although it has the developer tools now which makes the life a bit easier but what about it’s own implementation of DOM which is the only implementation and for this browser only,  a developer always feel forced to insert conditions in his/her code just so the code can work on IE as well. Anyway, this is something we all know about and can’t do anything until Microsoft thinks of something else. Let’s get to the business!

The problem with IE Select dropdown

IE Select dropdown width problem

If you are a web developer and you ever worked with a select dropdowns in your projects, you know this problem very well. IE by default don’t adjust it’s drop down panel according to the width of the longest text size. There are already work-arounds available and couple of them are based on YUI itself but they don’t mimick the exact look and feel of our old select dropdown list. I wanted the exact same look and  functionality of native SELECT element. I will list the work-arounds I founds:

Why a new fix?

Good question! The first fix I told you here, involves animation, when you get your mouse on the select element, it will animate itself to with of longest text option element, doesn’t look good to me as I think normal web user do not expect that to happen!

The second fix, had two major problems:

  • Scrollbars, my lists were very long as you will see in the example. By default YUI menu button’s scrollbars appear at the top and the bottom, if you increase the scroll rate for faster scroll, not very user friendly.
  • It didn’t had the important feature of selecting the options through keypress, I mean if you press B in normal select dropdown list, it will take you to the first item which is starting with B.

The fix itself!

Ok Ok, I think I am talking too much and few of my fellows will be in a hurry to get the code, copy paste it and get it done! Well, alright, the example is here:

Example: Work-around for fixed-width SELECT element

Explanation – The CSS

In fact I have modified the official YUI2 example.
First we need the same look as traditional select menu dropdown has. However if you want to make it more pretty, the CSS is here, do whatever you like to do, maybe using default arrow image of YUI is a better idea.

So, for the CSS, just as we override the default CSS rules of YUI skin sam:

.yui-skin-sam .yui-menu-button button {
    background-image: url("http://ciitronian.com/examples/images/select_arrow.PNG");
    outline: none;
    	font-size: 0.8em;
    	width: 15.2em;
    	background-color: #F8F8F8;
    	cursor: default;
    	line-height: 1.3em;
    	min-height: 1.45em;
	}
	.yui-skin-sam .yuimenuitemlabel:visited  {
	color:#000000;
	}

	.yui-skin-sam .yuimenuitemlabel  {
	color:#000000;
	cursor:default;
	padding:0 20px 0 0;
	text-decoration:none;
	}

	.yui-skin-sam .yuimenuitemlabel:hover  {
	background-color: #111166;
	color: #ffffff;
}

and a bit of tweak to YUI’s official example em class of button to make it look like a classic one

        /*  Restrict the width of the label to 10em. */
        width: 10em;

Our YUI menu button has got the classic look of SELECT element. Now the bit which I refer to as a nasty tweak, YUI menu button comes with it’s own scrollbars and as I mentioned earlier, if  you have a very long dropdown list, like a list of all countries in the world, the scrollbars functionality is not very useful. YUI gives you the flexibility to disable the scrollbars but I actually needed a vertical scrollbar so, what I did, I made the YUI scrollbars hidden!

Here’s what I did:

/*hide the YUI scroll bars*/
.yui-skin-sam .yuimenu .topscrollbar,
.yui-skin-sam .yuimenu .bottomscrollbar {
    height: 0px;
}

What it’s doing? Simply overriding the overriding the CSS property of height and setting it to 0. It hides the top and bottom scrollbars altogether. Next we need to turn the overflow to auto for yui menu body:

.yui-skin-sam .yuimenu .yui-menu-body-scrolled {
    overflow: auto;
    padding-right: 10px; /*to prevent horizontal scroll*/
}

If you just set overflow to auto, there will be just about a couple of pixels which will get over-flowed horizontally as well, to make it right, the padding property is there.  The padding-right property in div.yuimenu .bd is also there because IE8 was not getting right even if after the above rule.

Explanation – The JavaScript Code

The other JS code is almost the same as you will find in the official example but as mentioned earlier there were only two problems, the scrollbar, which we have already fixed through CSS, now the sorting or whatever you call it that when you click for a character, the first of it’s appearance should get selected automatically. So, all we need is basically a YUI keylistener with a correct scope and then we need to have a functionality which gets all the menu items text in an array, traverse the first character of each text element and then if matched, scroll to it and select it with the correct focus.

The keylistener code:

var kl = new YAHOO.util.KeyListener("select-1-container", { keys:keyArr },
			   { fn:labelCharCode,
				 scope:oMenuButton1,
				 correctScope:true } );
kl.enable();

The keylistener code select-1-container as first argument to give it correct scope. Notice the loops before this code in the source code of example, 65-90 are the keyboard character codes for A-Z characters and 48 to 57 are 0-9 characters. Next, keylistener calls the labelCharCode function with the keypressed event type, and arg[0] has the character which got pressed.

Now, the YUI Menu has a useful method of getting all items in a menu, as you see in the source code, I have used it to get the menu items:

var MenuItems = oMenuButton1.getMenu().getItems();

Now, in the labelCharCode, we have got the character which user has pressed, we need to match it with the first character of menu item. We access the text property of all menu items in a loop, slice the first character, match it in a if else block, if matched, simple old window.location takes us to the id of that menu item and then we fire the focus event for that menu item.

for (var i = 0; i < MenuItems.length; i++) {
			var firstChar = MenuItems[i].cfg.getProperty('text').slice(0,1);
			if(firstChar === Character){
				window.location = "#"+MenuItems[i].id;
				MenuItems[i].focus().fire;
				break;
			}

That’s it your SELECT dropdown element is ready which will work in all browsers and will adjust itself with the width of longest item in the menu!

Things which are still nagging me

The above code works as you may see in the example (I only tested that in IE8, Firefox 3.6 and Chrome 5), however I personally don’t like the concept of matching in a loop and slicing. I tried using YUI DataSource for this purpose but what I have seen in the YUI source code is that, it takes LocalDataSource array as it is and then apply matching on it, probably DataSource is more useful for remote data. However, I left it there after I tried seeing the code of AutoComplete widget and DataSource together, maybe I need to give it a bit more investigation. However, I dislike the current approach of this example because of processing involved. If you have a better understanding and know a better way of doing this, kindly embed the example or simply let me know, that will make this fix better!

Happy Coding!

YUI Dialog: Hiding the Dialog Panel in IE – Bug Resolved

This fourth installment in YUI series is basically about firing a bug which appears to be IE (Internet Explorer) only. This is when you have opened an iframe in dialog widget and showing another resource using frame.src and relying on native

close : true

parameter of YUI Dialog widget to show a close button to the user on top of the dialog and when user clicks it, your dialog panel get hidden. Works very well in Firefox and Chrome but when I went to test in my IE8 installation, it went wrong.

The Error

The error was this, when I was closing the dialog panel in IE, it was getting hidden but leaving some traces of it back, in most of the cases, border of a table which was in the iframe, meaning dialog was not getting properly hidden. It was also not allowing me to click or change any thing in the background elements  in the parent page, reason because IE was still thinking that dialog is on top of those elements.

The Code

The code I was using to pop up a new dialog panel to show a new regular page to the user in dialog iframe:

<script type="text/javascript">        
 // Instantiate the Dialog
 var addCons = new YAHOO.widget.Dialog("addCons",
 {   width : "965px",
 y: 15,
 x: 500,
 modal: false,
 visible : false,
 constraintoviewport : true,
 draggable : true,
 monitorresize : true,
 close : true});
 addCons.render();
 var DialogShow = function(e, args, o){
 var frame = document.createElement('iframe');
 frame.src = "http://www.ciitronian.com";
 frame.width = "100%";
 frame.height = "580";
 o.setBody(frame);
 };
 //subscribing to the on show event
 addCons.showEvent.subscribe(DialogShow, addCons);
 YAHOO.util.Event.addListener("showHL", "click", function(o) {addCons.show();});
 </script>

I tried couple of work arounds but this idea really works well that ofcourse the dialog panel’s body is having problems, then just set the body to null on hide event.  So I subscribed the hide function with the hideEvent of dialog:

var DialogHide = function(e, args, o){
 o.setBody(' '); //set the body to null to avoid IE problem
 };
 //subscribe the hide event
 addCons.hideEvent.subscribe(DialogHide, addCons);

Another requirement was to enable the user to press ‘Esc’ key to hide the dialog panel. We need a Key listener event then from yui utils to listen on key 27 (which is Esc key). Add the following bit of code just before we render the dialog widget:

var keylistener = new YAHOO.util.KeyListener(document, { keys:27 },          
 { fn:addCons.hide,
 scope:addCons,
 correctScope:true
 }, "keyup" );
 addCons.cfg.queueProperty("keylisteners", keylistener);

View the example source code to have a look at the full source code.

View YUI Dialog Example in New Window

YUI Calendar: Using and modifying multiple calendar picker instances on single page

In this third installment of how to create and modify existing widgets from YUI Javascript library, I will explain how to create multiple instances of calendar widget of YUI2. There are many examples of how to use Calendar widget on YUI’s official website but for my application, the requirement was to to schedule multiple actions on the same page, giving each one a date of its own, so I developed my calendar widget with the example found here.  As a matter of fact if you really want to include multiple elements of a same type on a HTML page, you assign them different ids so that they can be accessed and called appropriately (just like you do with your babies).

You can see in the example I have built on, we are using here the power of Dialog container of YUI, we are using its button and context features to support our pop-up calendar.

It’s best that you first see my example code working and then I will explain the code.

View Multiple YUI Calendar Example in New Window

If you see the source code of example, you will see what I have really done is that, I have passed a unique ID for each calendar instance through declaring

<script type="text/javascript">
calendar(1);calendar(2);
</script>

Then I enclosed all of the code in a function, which accepts the calendar ID and each variable which declares the calendar, gets embedded with this calendarID so that whenever a user uses any instance of the calendar, my widget knows about which calendar we are talking about and the calendar instance needs to update which fields. The same id is needed to be embedded in HTML fields which we are updating through (of-course using powerful features of DOM manipulation given by YUI)

Dom.get("month-field"+calendarID).value = month;
Dom.get("day-field"+calendarID).value = day;
Dom.get("year-field"+calendarID).value = year;

and at the end we really need to ensure that our HTML bits have those IDs

<span id="datefields1">
 <label for="year-field">Year:</label>
 <input id="year-field1" type="text" name="year" value="" style="width: 3em;">
 <label for="month-field">Month:</label>
 <input id="month-field1" type="text" name="month" value=""style="width: 2em;">
 <label for="day-field">Day:</label>
 <input id="day-field1" type="text" name="day" value=""style="width: 2em;">
 </span>

Note the input id parameter, which is year-field1, month-field1 and day-field1, you can create as many calendars as you want, if you have specified the input ids and declared calendars with those ids.

There is one other bit which I have manipulated is the default behaviour of calendar when it updates a field, I was required to have date stamps in the format of YYYY:MM:DD while the calendar would update the fields in the format of YYYY-M-D format, for this functionality there may be some call given by YUI Calendar widget itself because I have seen several type of formats floating around in examples section but for me it was really quick to modify my function to include preceding zeros (if there are none) to month and day before I populate the the DOM elements:

//This modification is done to cater calendar out from yui to
//give always the leading zeros for months and days, in case they
//dont have
 var month = ''+aDate[1]+''; //convert month into a string
 var day = ''+aDate[2]+'';    //converty day into a string
 var year = ''+aDate[0]+'';    //converty year into a string 

//embed the preceding 0 if month is of 1 digit
 if (1 === month.length) {month = '0' + month;} 

//emebed the preceding 0 if day is of 1 digit
if (1 === day.length) {day = '0' + day;}

Other than these you will see how this function is subscribing certain events. One important event is to hide the pop-up calendar when a user presses ‘Esc’ key

// Pressing the Esc key will hide the Calendar Menu and send focus back to
 // its parent Button
 Event.on(window["oCalendarMenu"+calendarID].element, "keydown", function (p_oEvent) {
 if (Event.getCharCode(p_oEvent) === 27) {
 window["oCalendarMenu"+calendarID].hide();
 this.focus();
 }
 }, null, this);

For the calendar I am using default button provided by YUI CSS, you can change it by declaring your own calendarpicker button style in CSS like:

#calendarpicker button {
 background: url(../../images/calendar_icon.gif) center center no-repeat;
 text-align: left;
 text-indent: -10em;
 overflow: hidden;
 *margin-left: 10em; /* For IE */
 *padding: 0 2em;    /* For IE */
 white-space: nowrap;
 }

change the background URL to your own image.

Feel free to comment if you have any better ideas to make this example more robust.

YUI Panel: Changing buttons and re-using a panel on same page

YUI is a natural Javascript library, it is different from jQuery and other Javascript frameworks due to its closeness to original JS where it supports you to build widgets and enhance the user experience, the other JS libraries such as jQuery and Dojo are incredibly easy to work in but they divert you so much from the original language that they become almost a language of their own. The closeness of YUI to the Javascript at the one end gives you the depth to work at your own ease and develop whatever you want, on the other hand it proves to be a bit difficult for the beginners, probably that is the reason, you will not find much support for YUI around, YUI has its own forums where the original developers of YUI reply in time-efficient manner as well but they speak at very high level and people to support at beginner and middle level are very less on Google.

So, I have decided to write some blog posts about the problems I faced and I was not able to find help on Google but then I figured out in some way (occasionally from the help of developers at yuilibrary.com where I got some hints and I made something out of it). I have already wrote first in this series which was about IE rendering problem in YUI tabs, today I will explain how you change modal panel buttons on the fly.

The requirement was to make a modal panel in YUI, which work at its own as a activation widget using Ajax (YUI connection manager). The idea was pop up a YUI panel when a user clicks on “Activate”, that modal panel will allow the user to enter details of when he/she wants to schedule the activation (where user can choose now or a later date) and then user can hit Submit and Cancel buttons. Uptil now its usual YUI panel which you will find in any YUI panel example, here is the example of making this panel:

//function to execute if user clicks on Cancel button
var handleCancel = function() {
 this.cancel();
 };

// Instantiate the Dialog
 var dialog23 = new YAHOO.widget.Dialog("dialog23",
 { width : "340px",
 fixedcenter : true,
 visible : false,
 modal: true,
 constraintoviewport : true,
 hideaftersubmit: false,
 buttons : [ { text:"Submit", handler:handleSubmit, isDefault:true },
 { text:"Cancel", handler:handleCancel } ]
 });
//delcare the call back
dialog23.callback = { success: handleSuccess,
 failure: handleFailure };
 // Render the Dialog
 dialog23.render(document.body);

//attach the listener to Activate (button, div,
//span where ever you want to attach the click listener)
YAHOO.util.Event.addListener("Activate", "click",
dialog23.show, dialog23, true);

A function of handleSuccess is missing in this example, I will leave that to you because in my code its very inter-related. The handleSuccess is where I make an Ajax request through YUI connection manager, once the request get submitted, I show a spinning wheel and then the Ajax response get shown to the user. The problem here is, you can easily show the response but changing the buttons at the bottom became a night-mare. Once the response is shown, you don’t really want to show the user Submit, Cancel buttons, what you really need there is a OK button so that user can acknowledge and on acknowledgment the panel hides away.

Here is how you change the button at the bottom of YUI panel:

//embed the OK button, on click handleOK function will be called
 dialog23.cfg.setProperty("buttons", [ { text:"OK",
handler:handleOK, isDefault:true }]);

and then write a funciton to act when user press Ok

//these are events which happen after a user clicks OK
 var handleOK = function() {
 //hiding the dialog box
 dialog23.hide();
 };

There is one other approach I am using in this panel, that is if once activated, the user clicks on the Activate/Deactivate button again, what will happen then? You really need to make it dynamic.

For example your panel HTML was this:

<div id="dialog23">
<div>Activation</div>
<div align="left"><div id="ajaxResponse">
<form method="POST" action="timeschedule.php">
 <input type="hidden" name="buttontime" value="1">
 <div id="resp"></div>
<div>Enter the date here: <input type=text value=date name=date>
</div>
</form>
</div></div>
</div>

In this case before initializing anything get the all the ajaxResponse innerHTML in a variable like:

var div = document.getElementById('ajaxResponse');
//we are storing schedule HTML which we will restore at the end
var divHTML = div.innerHTML;

Now our ajax response will change the bits you get in ajaxResponse div, the input text box and other will be changed something like:

User specified action has been activated.

What you need now is to restore the original panel just when the user clicks OK, the handleOK function will get changed to this:

//these are events which happen after a user clicks OK
 var handleOK = function() {
 //restore the schedule HTML in case user wants to use it again
 div.innerHTML = divHTML; //populating the initial schedule HTML back
 //hiding the dialog box
 dialog23.hide();
 //rebuilding the submit and cancel buttons for re-use
 dialog23.cfg.setProperty("buttons", [ { text:"Submit",
handler:handleSubmit, isDefault:true },
 { text:"Cancel", handler:handleCancel } ] );
 };

And here you will replace the buttons in the bottom of the panel again to Submit and Cancel.

I have elaborated enough here to give you an idea of how things work, its possible you are not working on same thing but my idea was really to give you ability to change buttons in the panel on the fly and to give you an idea that how you can use a panel again and again. If you need any further help, don’t hesitate to comment and I will try to reply as much as I can.