Make Your Own Plugin, Part 1: Writing Custom Controls
Once you have used QCubed for a while, you may find you have a need to create your own custom Control, or connect QCubed to an already existing javascript widget.
Start by familiarizing yourself with the various plugins currently written. Take a look at their source code and how the directories are structured. Also take a look at the QCubed core files that are implementations of JQueryUI components, like the Autocomplete component.
There are a many ways to write a custom widget. A couple have already been explained under Creating Your Own Control and Creating a Composite Control. These methods modify currently available QCubed controls. Studying these examples will give you a good understanding of how controls work on the PHP side.
Connecting Javascript Controls to QCubed
There are now many javascript libraries available on the internet that contain a huge variety of javascript controls. These controls can be connected to QCubed so that all the functionality of the control is available to you within PHP. Below is a discussion of some of the aspects of connecting a javascript control to QCubed. For the most part, you can follow the examples of the currently written libraries and JQueryUI components. The basic goal is to maintain sychronization between the javascript control's data, and the data in the PHP control.
Setup
First, you must decide on what the base HTML object of the control will be, and this will determine what the base of your Control wil be. For example, if your javascript widget attaches to a <div> block, you should have your control extend from the \QCubed\Control\Panel class, because the \QCubed\Control\Panel outputs a <div>. If you have additional HTML that you need to output, implement a getControlHtml method in your control.
QCubed includes jQuery and uses it for much of its internal function. However, it changes the '$' variable to '$j'. So, to refer to your control for example, you would output the following javascript:
$j('#controlId')
where controlId is the id of your control. You can get your id by accessing the ->ControlId parameter of your control object in PHP.
Make sure you include the javascript for the widget itself by calling addJavascriptFile from your PHP constructor.
Moving Data from PHP to Javascript
How you move data from PHP to Javascript will depend a bit on your javascript widget.
- If your JavaScript widget uses a JavaScript function to attach itself to an html object, you should implement the makeJqOptions() method. This method connects parameters for your initialization method with Properties you make available through __get and __set methods.
- If instead your JavaScript widget uses html attributes to define its data, implement the getControlHtml() method, and add the attributes in the attribute overrides when calling renderTag().
- If neither of these work for you, you can override the getEndScript method to output the javascript that will connect the javascript widget to the HTML you created. This is also the place where you would output any class member variables of your PHP in such a way that the javascript will read it. Whenever your control is completely redrawn, all the HTML and the javascript in getEndScript will be drawn again, updating the control to the current data in PHP. To cause a redraw, call the refresh() method, which it inherits from \QCubed\Control\ControlBase. See examples of this is done in the various Gen classes in the Jqui directory.
When you want to update your control after initially drawing it, use the addAttributeScript() method. This will execute a javascript function on your control that you would use to do the update. It is also designed so that if the entire control is redrawn, the javascript will not execute, since it will not be needed since the whole control is being drawn. For example, you could do this:
$this->AddAttributeScript('val', $strValue);
to set the value of your control to $strValue on the javascript side. Remember to also save that data into your PHP control, because later your control might completely redraw, and it will need to draw using the new value.
Moving Data from Javascript to PHP
Data comes from the javascript via a variety of mechanisms.
The primary method is via Post variables. All input items, like textboxes and checkboxes are submitted this way. Also select items like lists send there data this way.
To read post variables, create a subclass Control in PHP and override the parsePostData() method. Within that function, examine the $_POST superglobal and update your internal state accordingly.
To have your javascript send POST variables, you can do one of the following:
- Atttach your control to a standard html form element, like a textbox or checkbox and on the PHP side, make your Control a subclass of the corresponding Control type. The data will automatically be updated in QCubed. See \QCubed\Project\Jqui\Checkbox for an example of this method.
- Create a hidden input element and store the data that the javascript widget represents into the hidden element. Update that hidden element in javascript whenever the data changes, but also trigger the qformObjChanged event to notify QCubed that the control changed. For the hidden input(s), give them an id that is the same as the parent control, followed by an underscore and whatever text you need to uniquely identify it. This will associate the hidden control with the parent control. Implement the parsePostData() method within your control to read the data in the post variable and put it into your Control.See the \QCubed\Control\ImageInput control source for an example of this method.
- Whenever your control changes, call qcubed.setAdditionalPostVar(name, val). This will add the name and value to the list of variables posted. val can be a string, array or object. If you cannot detect a change for some reason, you can add a listener on the qposting event on the form. That event is fired right before any post variables are sent to PHP, which gives you a good opportunity to call qcubed.setAdditionalPostVar.
Another way to push data to PHP is to use the qc.recordControlModification function whenever an aspect of your control changes. Call qc.recordControlModification from your javascript control and pass it your control id, a control property, and a new value for the property. The control property is a property in your PHP control that you can set through the __set magic method. Calling recordControlModification in javascript will cause QCubed to pass that value to your PHP control through that property.
See the \QCubed\Project\Jqui\Accordion control for an example of this method.Events can also be used to read data from the control during the processing of a specific event as discussed in the jQuery Controls: Adding Actions example.
Many javascript controls these days use ajax or javascript mechanisms to read data in real time. Connecting these controls to QCubed is more complicated, and is dependent on the specific implementation. Here are some examples:
- Use a special NoScript ajax action to link a javascript function with a PHP \QCubed\Event\EventBase. The event gets triggered when the javascript widget wants data. Within that event, execute a \QCubed\Project\Application::executeJavascript() command to send the data back to the widget. This is how the \QCubed\Project\Jqui\Autocomplete widget works as well as the datatables plugin.
- Create a php file that outputs json data that you want to send to the javascript widget. Set the name of the php file in the javascript widget as being the source of its data. If you initialize QCubed within that php file, you will have all of the QCubed functionality available for generating the json file.
- Create a php file that outputs a javascript array and include that file in your html so that whenever the page is drawn, it updates the array.
Adding Your Control to the Code Generation Process
If your control is designed to edit a basic type that might come from the database, like an integer, varchar string, or even a list of items that are a result of a primary key relationship, you can add some code to your control to include it in the code generation process to make it easier for other users to bind your control to fields in the database.
If you have based your control on a type that binds to a pre-existing control, you might not need to do anything extra. The \QCubed\Project\Jqui\SelectMenu control is an example of this, which is simply a javascript overlay on a list box.
However, if your interface is a unique way of editing data, you will need to create a new class called ${LIBRARY_CONTROL_CLASS}_CodeGenerator that extends AbstractControl_CodeGenerator (or more likely Control_CodeGenerator) to bind the control to the database. See the Slider_CodeGenerator for an example of how to generate the binding code needed.
Implement the GetModelConnectorParams function to allow a user of the control to set all of your __set parameters through the ModelConnector Designer user-interface. This will make it much easier for users to know and use the capabilities of your control.
Finally, in order to make your control available to the designer as an option for the particular data types your control manipulates, create a control_registry.inc.php file and put it in the /install/project/includes/configuration/control_registry directory. Name it "your_control_name".inc.php.
Read the next chapter to learn about ways to package and distribute your plugin.