Escape from Default’s Yii2 Delete Confirm Box

EDIT: Article has been updated with latest changes on Yii2 yii.js API.

If you started playing with Yii2’s new GridView, you will soon realize that some things have improved quite a lot, but I am sure its going to be a bit confusing at first glance when you try to modify certain javascript behaviors like for example, its delete confirmation dialog box.

I am sure there will be others finding better solutions, but on my case, I found the following quite simple to execute. On this article I am going to show you how to use the excellent library “BootboxJS” to display our confirmation box.  So, the first thing you need to do is to download that library and place it on your “web/js” folder (I am using the basic template application structure for the example).

Override Yii.js Module

The first thing we need to do is to override the methods “confirm” and “allowAction” on the yii.js module. The reason is that the module is the responsible of handling the click events of our rendered ActionColumn action links.

In order to override its methods, lets create a javascript file named “main.js” (for example) and we are going to place it on our “web/js” folder. These are the contents:

yii.allowAction = function ($e) {
    var message = $e.data('confirm');
    return message === undefined || yii.confirm(message, $e);
};
yii.confirm = function (message, ok, cancel) {
    bootbox.confirm(message, function (confirmed) {
       if (confirmed) {
         !ok || ok();
       } else {
         !cancel || cancel();
       }
    });
    return false;
}

The way the module comes by default is by using the native browser’s confirm dialog window that obviously stops the asynchronous execution of Javascript, but when you try to use “bootbox::confirm” method, that is not happening as the callback of the plugin does not return until the user clicks on one of the dialog’s buttons.

Thats the reason we override the “yii::confirm“, because we require the reference of the DOM object in order to call “yii::handleAction” properly. But is “yii::allowAction” the method who receives that reference, and thats why we had to override that method as well.

With this solution, the “yii::allowAction” will return false the first time, but “bootbox::confirm” will react according to user’s action. Thats the price you pay, but it works.

Register Your Assets

There is one last thing to do, modify our application’s asset bundle to register both “bootbox.min.js” and “main.js” script files.

For that we go to our “assets/AppAsset.php” file and write the following:

namespace backend\assets;

use yii\web\AssetBundle;

class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = ['css/site.css'];
    // register the library first after our
    // script
    public $js = ['js/bootbox.min.js', 'js/main.js'];
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}

And thats it, next time we click on the delete action of our grid, we will see a beautiful Bootstrap Confirm dialog.

bootbox dialog

How to use BootstrapInterface – Yii2

Yii2 introduces the BootstrapInterface to ease our application initialization tasks, whether they are `composer` based or Application bootstrap (do not mistaken with Bootstrap CSS Framework) based tasks. On this article, I am going to explain how to use it on your Application bootstrap process.

For our example, our site has two themes, one for desktop and the other for mobile rendering respectively, and we wish to switch between themes after detecting user’s platform. I am not going to explain how to install and configure your Yii2 Application (for a brief hint, please check my previous post on Yii2: Adding FontAwesome to your Yii2 Project Via Composer) so I assume that you have already a Yii2 application ready folder structure.

Install required extensions

In order to check whether our visitor is using a mobile, tablet or desktop device, we are going to use the wonderfully made class Mobile_Detect from mr Serban Ghitta (Thanks buddy!). In order to do that, we are going to include the following on our composer.json file:

"require": {
// ....
"mobiledetect/mobiledetectlib": "dev-master"
// ....

},

And run your composer update command at your terminal window.

Configure your desktop theme

Yep, before we forget, we need to configure our desktop theme. On this example, I am using the basic project template from Yii2, so the file where I am configure my theme is on config/web.php.

'view' => [
    'title' => '2amigOS! Consulting Group LLC',
    'theme' => [
       'baseUrl' => '@web/themes/desktop',
       'basePath' => '@app/themes/desktop'
    ]
],

As you can see, my themes are located under the application’s root directory and within the folder named themes. Note: Even though it is not shown here, I have another theme named mobile under the same folder themes.

The ThemeBootstrap class

Ok, now that we have configured our desktop theme, lets code our ThemeBootstrap class. This class will extend from the already mentioned BootstrapInterface interface and as you can see it has only one public method that is required to be implemented: bootstrap. This method is fired on our application’s init function per bootstrap class configured. For a closer look on how and where it occurs, please check Application::init method at github.

The following is our ThemeBootstrap class, placed in our app/extensions/components folder:


namespace app\extensions\components;

use yii\base\Application;
use yii\base\BootstrapInterface;

class ThemeBootstrap implements BootstrapInterface
{
    /**
    * Bootstrap method to be called during application bootstrap stage.
    * @param Application $app the application currently running
    */
    public function bootstrap(Application $app)
    {
        // create an instance of Mobile_Detect class
        $detector = new \Mobile_Detect();

        if ($detector->isMobile() || $detector->isTablet()) {
        // modify the configuration of our view component
        $app->set('view', [
            'class' => 'yii\web\View',
            'title' => '2amigOS! Consulting Group LLC',
            'theme' => [
                'baseUrl' => '@web/themes/mobile',
                'basePath' => '@app/themes/mobile'
            ]
        ]);
        }
    }
}

You probably are wondering what is going on? How come we change the component’s configuration like this? Well, in case you don’t know, Yii2 now supports DI (dependency injection) and our Application component extends from a Module, which extends from DI’s ServiceLocator class (check ServiceLocator::setComponents and Application::preInit functions, and you will soon realize how the Application merges the required classes with their configuration).  What we are using, is the ServiceLocator::set method to override the configuration of the view.

Configure ThemeBootstrap

There is one more thing to do, we need to tell our Application that we wish the ThemeBootstrap::bootstrap to run when initialized and we do that by setting Application::bootstrap property.

// ...
'bootstrap' => [
    'app\extensions\components\ThemeBootstrap',
],
'components' => [
// ...

And that’s it, now when your application starts Yii will automatically call ThemeBootstrap::bootstrap method thus modifying the theme of your application.

YiiBooster: How to implement files and images upload on TbRedactor Widget

Introduction

I have been requested to provide a tutorial on how to use the file upload feature of the TbRedactor widget, and even though I am no longer supporting the library due that the work belongs to Clevertech, I feel that I owe that to the Yii developers. So, here is the  tutorial.

How to do it

As you know, if you try to use TbRedactor “out of the box” and if you click on the “insert image”, you will see a “popup window” as the following image:

Captura de pantalla 2013-03-24 a la(s) 15.34.57

Not bad… but too plain. What about the really cool Fileupload shown on redactorJS site? How do we set it up? The answer is easier that you can imagine. In fact, the only thing that we need to tell the plugin is the URL address that will handle file and image upload processes.

// with a TbActiveForm
echo $form->redactorRow(
    $model,
    'attribute_name',
    array(
       'class'=>'span4',
       'rows'=>5,
       'options'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
       )
    )
);

// as a widget
$this->widget('bootstrap.widgets.TbRedactorJs',
    array(
      'model'=>$model,
      'attribute'=>'attribute_name',
      'editorOptions'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
          'width'=>'100%',
          'height'=>'400px'
       )
    ));

As soon as we have that setup, the popup window will display differently:

Files Upload
File Upload
Images Upload
Images Upload
Important How to handle file uploads is out of the scope of this tutorial. If you wish to know how to handle file uploads on the server from redactorJS, I highly recommend you to read the documentation at imperavi.

But wait a minute, what about if we wish to insert an image that has been previously uploaded? If we look at the redactor’s site we find really cool thumbnail images to choose from. Well, is a bit tricky but not too hard. The only thing that you have to do is to provide a URL where to download the “json” file that will get the uploaded files and their location.

// with a TbActiveForm
echo $form->redactorRow(
    $model,
    'attribute_name',
    array(
       'class'=>'span4',
       'rows'=>5,
       'options'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
          'imageGetJson'=> $this->createUrl('site/testImageThumbs')
       )
    )
);

// as a widget
$this->widget('bootstrap.widgets.TbRedactorJs',
    array(
      'model'=>$model,
      'attribute'=>'attribute_name',
      'editorOptions'=>array(
          'fileUpload' => $this->createUrl('site/testFileUpload'),
          'imageUpload' => $this->createUrl('site/testImageUpload'),
          'imageGetJson'=> $this->createUrl('site/testImageThumbs')
          'width'=>'100%',
          'height'=>'400px'
       )
    ));

The following is an example action to return the thumbs and image information:

public function actionTestUpload()
{
   echo json_encode(array(
        array(
           'thumb' => '/images/image1_thumb.png',
           'image' => '/images/image1_big.png',
           'title' => 'Title1', // optional
           'folder' => 'myFolder' // optional
        ),
        array(
           'thumb' => '/images/image2_thumb.png',
           'image' => '/images/image2_big.png',
           'title' => 'Title2', // optional
           'folder' => 'myFolder' // optional
        )
   ));
}

Obviously, the above example has to contain the right resources to work but the result would endup with something similar to the following:

Images Choose
Images Choose Option

Further Reading


[ad]

YiiSES – Implement Bulk Mail Marketing with Amazon Simple Email Service

Introduction

I had to develop a module for a client at Clevertech that was quite interesting and not any developed for Yii yet. The module was to take care of the bulk mail services of the client and it supposed to work with nearly new Amazon Simple Email Service, which make the task even more interesting. The end result was YiiSES and to tell you the truth I was very happy with it.

Nevertheless, an image is worth thousand words, check the following thumbnails:

Features List

  • Campaign Management
  • Campaign Stats
    • How many opened / failed / successful
  • Amazon SES Usage Stats
  • Subscriber Management
  • Google track vars
  • And much more

I think is much better if you check it out by yourself. The whole project is Open Source and is freely available under Creative Commons Attribution-ShareAlike 3.0 Unported License, you can get is source code at Clevertech’s github profile: https://github.com/clevertech/YiiSES, we would love to hear your feedback, specially me.

YiiSES Overview

YiiSES is a ‘nearly’ ready to use module that provides our Yii applications with a powerful mail marketing tool.

The system makes use of Amazon Simple Email Service (Amazon SES), chosen for its highly reduced price, scalability and features. A developer can easily register and use its service at no charge. The only issue is that the developer will only be able to send to verified email addresses only.

This module has been ported to Open Source from one of the projects at Clevertech and we are very happy with its results so far.

Install

Even though there was some good effort to make it easy to install, its configuration is a bit tricky. We hope this will change with a bit of help from the community.

Within the code you will find two types of configurations, one to be used with YiiBoilerplate project structure (default), and the other when we install the module on a Yii’s default application structure.

The following instructions are to install the module on a Yii’s default application structure.

Configuration

  • Create your web application ./yiic webapp <folder>
  • Create your modules folder under protected and unpack the module there
  • Place commands\CampaignCommand.php on your commands directory and ses\migrations.. on the migrations directory.
  • Configure your config\main.php and config\console.php configuration files (make sure your db is setup correctly –migrations will only work on mySQL db):
// console.php
    …
    // urlManager must be the same configuration as on main.php
    'urlManager' => array(
        // remember to have your .htaccess setup correctly
        'urlFormat' => 'path',
        'showScriptName' => false,
        'urlSuffix' => '/',
        'rules'=>array(
            '<controller:\w+>/<id:\d+>'=>'<controller>/view',
            '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
            '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
        ),  
    ),
    // request is required to create appropriate urls within the emails
    // when processed with the CampaignCommand.php
    'request' => array(
    )
    …

    // main.php
    …
    'modules' => array(
        'ses'=>array(
            'password'=>'clevertech',
        ),
    ),
    …   
    // urlManager must be the same configuration as on main.php
    'urlManager' => array(
        // remember to have your .htaccess setup correctly
        'urlFormat' => 'path',
        'showScriptName' => false,
        'urlSuffix' => '/',
        'rules'=>array(
            '<controller:\w+>/<id:\d+>'=>'<controller>/view',
            '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
            '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
        ),  
    ),  
    …
    'params'=> array(
        'ses.aws.key'=>'',
    …
  • Setup required parameters on your configuration files (must be shared among console.php and main.php):
    • ses.aws.key: Your Amazon SES API Key
    • ses.aws.secret: Your Amazon SES Secret
    • ses.aws.verifiedEmail: You are required to verify at least one email address on Amazon SES console in order to send emails
    • ses.aws.test.email: Email where to send test emails. If your account is not in production, then this email should be also verified on Amazon SES console.
    • ses.emailonacid.key: If you wish to preview and test your emails on EmailOnAcid service, you have to configure your API key here.
    • ses.emailonacid.pwd: Your password to access EmailOnAcid service for testing and previewing your emails.
    • ses.files.path: Set this parameter in order to make use of the image upload capabilities ofRedactorJS, our chosen WYSIWYG editor for the module.
    • ses.files.url: Required to display the thumbnail gallery of RedactorJS.
    • ses.panel.login.url: The Route to the control panel. This parameter is required at the unsubscribe form. If somebody tries to remove an email from a registered user, then it will be presented with the link created from this route, to inform that he should log into its panel in order to unsubscribe.
    • ses.from.name: Your application name. Will be included in the Emails From Name (ie.”My Application ” )
  • Make sure your paths are correctly set:
    • CampaignCommand.php::init() – line 74
    • CampaignCommand.php::sendEmail() – line 376
    • models\Email.php::formatHTMLMessage() – line 136
    • models\Template.php::getView – line 65
  • Run migrations ./yiic migrate from your command line prompt.

The User table and Model

One important part of the configuration is how you deal with the subscribers. The module works by default with the assumptions that your application has a User model with id, email, and username, subscribedattributes.

The module comes with a single field for campaign that configures whether to send to subscribers or not but you can easily change that behavior to match different fields or subscription option from your registered members -but this is something that I won’t explain here :).

Requirements

The following requirements are all included already within the module:
– EPHPThumb – SES Component
– YiiBootstrap
– HighCharts
– RedactorJS

Contribute

If you wish to contribute, please fork our repository!


[ad]

ENexmo Yii Extension Library

Introduction

ENexmo Library allows Yii programmers to use the Restful API offered by Mobile Messaging provider Nexmo.

ENexmo is a cloud-based SMS API that lets you send and receive high volume of messages at wholesale rates.

Requires

Quick Start

Once you have created your Nexmo account and include ENexmo library and EHttpClient on the extension folder is as easy as this:

/* import extensions before any call */
Yii::import('ext.httpclient.*');
Yii::import('ext.nexmo.*');

/* to send a message */
$nexmo_sms = new ENexmoSms('YOURAPIKEY','YOURAPISECRET');

$response = $nexmo_sms->sendTextMessage('RECIPIENTSNUMBER','SENDERID','Howdy testing! Please, search on http://www.google.com.');

/* responses are on JSON or XML. Defaults to JSON, but you can change that */
/* we use CHtml::encode for demo purposes only, to check for responses */
echo CHtml::encode($response);

/* to make requests for account */
$nexmo_account = new ENexmoAccount('key','secret');
$nexmo_account->format = ENexmoBase::FORMAT_JSON;

/* to search for a message */
echo CHtml::encode($nexmo_account->searchMessage('09AFDA98'));
//echo CHtml::encode($nexmo_account->searchMessagesByIds(array('09AFCC5B','09AFDA98')));
//echo CHtml::encode($nexmo_account->searchMessagesByDateAndRecipient('2012-03-20','34607040932'));
//echo CHtml::encode($nexmo_account->balance);
//echo CHtml::encode($nexmo_account->ownNumbers);
//echo CHtml::encode($nexmo_account->getSmsPricing('ES'));

Please check Nexmo API Documentation Nexmo API Documentation

Ei18n Translation Module

Introduction

Ei18 is a translation module to allow Yii’ers to easily manage missing translations on application pages. It is heavily inspired by yii.translatedeveloped by Gustavo Salomé Silva gusnips.

The reason I developed a new one is because Gustavo’s approach wasn’t suitable for my needs, I wanted the translation to be specific to the languages I was editing and not to edit them all at once. Also, the theme was not rendering right and I couldn’t make it work properly as my design was not developed with the one Yii comes by default.

I wanted to make it as a component only but then I realized that to be a module was the right way to go as I could automate language edition without making the user tweak far too many things.

The following is a snapshot of the editor:

 

Features

  • Automatically creates the message translation tables on your database.
  • The module allows the edition of translations specified by categories. Its utility widget WTranslate handles that very smoothly.
  • All is AJAX based at the frontend. I thought that would be better to leave to developers the creation of a backend in order to edit translation on a regular basis.
  • Its Ei18 component has a couple of helper functions to also manage language setting automation. Check also its Set CAction class to be included on the controller that will handle language setting.

Requirements

  • jQuery v1.7 or higher (tested with jQuery v1.7)
  • fancybox 2.0.5
  • jwysiwyg 0.97.2
  • jbar Jquery plugin –Modified version
  • Yii 1.9
  • Translations handled by CDbMessageSource

Tested With

  • Chrome 17.0.963.56 on Macosx Lion
  • Safari Version 5.1.3 (7534.53.10) on Macosx Lion
  • Firefox 8.0.1 Macosx Lion
  • Firefox 9.0.1 Macosx Lion

Quick Start

The configuration is a bit troublesome, but once you get it up and running editing your missing translations will be peanuts.

In order to make it run we need to:

  • Configure Ei18n on your main.php configuration file
  • Render the widget on your base layout file

Configuring Ei18n

On you main.php configuration file do the following:

/* import the module */
   'import'=>array(
   /* ... */
       'application.modules.translate.TranslateModule'
   /* ... */
/* setup your default language */
    'language'=> 'en',
/* setup message translation method */
    'components'=>array(
       'messages' => array(
           'class' => 'CDbMessageSource',
           'onMissingTranslation' => array('Ei18n', 'missingTranslation'),
           'sourceMessageTable' => 'tbl_source_message',
           'translatedMessageTable' => 'tbl_message'
           ),
/* setup global translate application component */
       'translate' => array(
           'class' => 'translate.components.Ei18n',
           'createTranslationTables' => true,
           'connectionID' => 'db',
           'languages' => array(
                    'en' => 'English',
                    'es' => 'Español',
                    'it' => 'Italiano'
                    )
            ),
        ),
/* setup the module */
       'modules' => array(
            'translate'
        ),
/* preload the global translate application component */
       'preload'=> array(
            'translate'
        )
        /* ... */

Displaying the editor

Once the module and the translation component have been set. You just use any of the following helper functions:

Yii::app()->translate->renderMissingTranslationsEditor();
    /* or only certain categories*/
/* Yii::app()->translate->renderTranslationsEditor(array('index','menu')); */

The first method and due to the view rendering nature of Yii, I highly recommend the display of those functions at the bottom of your main or base layout, as it will collect all missing ones throughout the rendering processes.

Remember the translations are automatically collected when Yii goes through all thoseYii::t(‘category’,’translation text’); statements throughout your code and fires the onMissingTranslation event.

Yes, is on Github

Anybody willing to help improving E1i8n is highly welcome. Check the github repository on the resources section.

Changelog

  • version 1.0.0 Initial version release

Resources


[ad]

EScriptBoost Extension for Yii

Introduction

Probably a lot of you would wonder why, having so many good extensions related to minifying/compressing/packing your javascript code and your css files at the Yii Extensions Repository, here comes this guy offering us another solution.

I did check out all the extension in our repository, just to name some of them:

All of them are great, but none were filling the requirements we had. I did not have any issue compressing all our files as our team, will use the YUI compressor jar file to create our compressed javascript versions and then use the wonderful mapping features of CClientScript. The issue was with the assets of external, or own developed, extensions and the javascript code that, even Yii own widgets, were writing to the POS_BEGIN, POS_END, POS_HEAD, POS_LOAD, POS_READY positions. Thats exactly what this library is doing: allowing Yii coders to minify those scripts.

I have created a GitHub repository for those willing to contribute on any of the extensions I created. Please, check the link at the bottom of this wiki.

Library

The library comes with three flavors:

  • EScriptBoost Component
  • EClientScriptBoost Extension
  • AssetManagerBoost Extension

EScriptBoost Component

This is a very easy to use component to compress your Javascript or CSS code at your will. The minifiers used are:

Usage

// this is a very simple example :)
// we use cache as we do not want to
// compress/minify all the time our
// script
$js = Yii::app()->cache->get('scriptID');

if(!$js)
{
     $cacheDuration = 30;
     $js = <<<EOD
     // my long and uncompressed code here
EOD;
     // $js = EScriptBoost::packJs($js);
     // $js = EScriptBoost::minifyJs($js, EScriptBoost::JS_MIN_PLUS);
     $js = EScriptBoost::minifyJs($js, EScriptBoost::JS_MIN);

     // see Cache guide for more options | dependencies
     Yii::app()->cache->set('scriptID', $cacheDuration);
}
Yii::app()->clientScript->registerScript('scriptID', $js);

That was troublesome right? No worries, if you don’t really care about using JS_MIN, or JS_MIN_PLUS, you can use its helper function registerScript, it will handle all of the above automatically:

$js = <<<EOD
    // my long and uncompressed code here
EOD;
EScriptBoost::registerScript('scriptID', $js);

EClientScriptBoost Extension

EScriptBoost was good for the javascript code written by me but what about the ones written by Yii widgets?EClientScriptBoost was developed to solve that:

Usage

On your main.php config file:

'import' => array(
// ... other configuration settings on main.php
// ... importing the folder where scriptboost is
    'application.extensions.scriptboost.*',
// ... more configuration settings
    ),
// ... other configuration settings on main.php
'components' => array(
     'clientScript' => array(
// ... assuming you have previously imported the folder
//     where EClientScriptBoost is
         'class'=>'EClientScriptBoost',
         'cacheDuration'=>30,
// ... more configuration settings

Done! now, every time you or other component on your application will be minified and cached as you specify on your cache settings. Easy right?

EAssetManagerBoost Extension

But there was one more challenge to solve. Some extensions, widgets, etc, do publish a whole bunch of files in our assets that are not minified. This is where EAssetManagerBoost comes handy.

This extension does only minify javascript/css files, and also makes sure that the, about to be compressed, file do not match any of its $minifiedExtensionFlags so minified/compressed files are not processed at all.

Usage

Make sure you have deleted your previous assets folder contents.

'import' => array(
// ... other configuration settings on main.php
// ... importing the folder where scriptboost is
    'application.extensions.scriptboost.*',
// ... more configuration settings
    ),
// ... other configuration settings on main.php
'components' => array(
    'assetManager' => array(
// ... assuming you have previously imported the folder
      'class' => 'EAssetManagerBoost',
      'minifiedExtensionFlags'=>array('min.js','minified.js','packed.js')
        ),
// ... more configuration settings

Important Note There is a small drawback to use EAssetManagerBoost and is that, the first time your application is requested, it will take a bit of time as it will go throughout all your asset files to be published and minify them.

Resources


[ad]

Avoiding duplicate script download when using CActiveForm on Ajax calls

Introduction

Sometimes the active form we wish to use to edit/add a new element on our database is too small and we believe that is much better to use an AJAX’ed dialog/slide form rather than reloading the page to just display one or two fields.

The only thing required is simple, we just need to create a view that will be partially rendered by a call to a controller (using renderPartial) and make sure that we process output -setting to true the parameter on the function. Everything will work as expected but…

The issue

If we open firebug (firefox), or developer tools (chrome), or whatever the tool you use in order to see the XmlHttpRequest object calls and resources downloaded, you will see that every time we do call the controller to display the active form, different Yii “core JS” files keeps being downloaded to the client. The JS files downloaded depends on your code but there are at least jquery.js, jquery-ui.js and jquery.yiiactiveform.js.

The solution

The solution is a bit tricky but simple. We need to pre-render the jquery.yiiactiveform.js on the view where we are going to place the AJAX functionality (the button that opens the modal dialog or slides/shows a layer with AJAX’ed form contents). For example, on index.php view file:

cs()->registerCoreScript('yiiactiveform');

Now, I assume that you have created your function to display the AJAX’ed active form and its contents are returned by a call to a controller’s action that will partially render a view. This is what we have to do in our action:

// Just before rendering the view that
// has our activeform
Yii::app()->clientScript->corePackages = array();

It is very important that we set corePackages to array() instead of null, as setting it to null will make CClientScript to reload the packages.php file (located in framework/web/js/) and we won’t stop the duplication of the script.

And that’s it, everything is working as it should.


[ad]

Offertutti

Coupon aggregator site I worked for. Duties I had for the company:

  • Consulting
  • CSS redesign
  • XML feeds development -Automation with PHP
  • RSS development and automatic marketing integration with Twitter and Facebook

 

Even though was not a Yii-job, I am more than happy to promote www.offertutti.com here as one of the most pleasant jobs I have ever had in my, already long -gosh how time pass, coding career.

The job was quite challenging, as any site developed by companies from certain countries, but managed to fulfill most of their expectations. Even though I am currently in the middle of a dream project, it continues to be a pleasure to help them out every now and then.

New design is coming…

Custom Autocomplete Display and Value Submission

 

Introduction

How many of us has wondered how to create an autocomplete that will display the names of a related models but do require the id of that selected name to be submitted for model creation/update?

I was looking around wiki and found that was no approach as the one I did so I guessed this is worth to write.

Requirements

For our example, I want to be able to:

  • Have an autocomplete field in our form
  • Once user selects an item in the dropdown list and fill a hidden box with the id of the selected item for submission

Making the right choice

To setup the autocomplete was a very straight forward operation, but I couldn’t figure out how to get values from a custom JSON response and then fill the correspondent hidden fields.

CAutoComplete does has a way to do it, but I wanted to use CJuiAutoComplete to get all the cool features of its JQuery Ui and by looking at his code there was no method chain, something that is required to work with custom JSON responses as we need to override some methods.

My Solution

After doing some research I decided to:

  1. extend from CJuiAutoComplete
  2. include the required property for method chain and modify its ‘run’ function
  3. then initialize the newly created property with the javascript functions that handle my custom JSON

Extending from CJuiAutoComplete and make required modifications

Very simple, we are going to add a methodChain property and modify the run function to include it (zii is not a major concern to Yii, but main developers should think about this minor change).

class myAutoComplete extends CJuiAutoComplete
{
    /**
     * @var string the chain of method calls that would be appended at the end of the autocomplete constructor.
     * For example, ".result(function(...){})" would cause the specified js function to execute
     * when the user selects an option.
     */
    public $methodChain;
    /**
     * Run this widget.
     * This method registers necessary javascript and renders the needed HTML code.
     */
    public function run()
    {
        list($name,$id)=$this->resolveNameID();

        if(isset($this->htmlOptions['id']))
            $id=$this->htmlOptions['id'];
        else
            $this->htmlOptions['id']=$id;

        if(isset($this->htmlOptions['name']))
            $name=$this->htmlOptions['name'];

        if($this->hasModel())
            echo CHtml::activeTextField($this->model,$this->attribute,$this->htmlOptions);
        else
            echo CHtml::textField($name,$this->value,$this->htmlOptions);

        if($this->sourceUrl!==null)
            $this->options['source']=CHtml::normalizeUrl($this->sourceUrl);
        else
            $this->options['source']=$this->source;

        $options=CJavaScript::encode($this->options);

        $js = "jQuery('#{$id}').autocomplete($options){$this->methodChain};";

        $cs = Yii::app()->getClientScript();
        $cs->registerScript(__CLASS__.'#'.$id, $js);
    }
}

Using our widget

Now that we have our beautiful widget that handles method chain in our Autocomplete, let’s assume a couple of things:

  • We saved our class onto a folder in our application -ie protected/extensions
  • We have a hidden INPUT HTML element with model’s attribute_id
  • We have created an action on our testController named autocomplete that returns a JSON object on the following format:
// This function will echo a JSON object
// on this format:
// [{id:id, name: 'name'}]
public function actionAutocomplete(){
      $res = array();
      $term = Yii::app()->getRequest()->getParam('term', false);
      if ($term)
      {
         // test table is for the sake of this example
         $sql = 'SELECT id, name FROM {{test}} where LCASE(name) LIKE :name';
         $cmd = Yii::app()->db->createCommand($sql);
         $cmd->bindValue(":name","%".strtolower($term)."%", PDO::PARAM_STR);
         $res = $cmd->queryAll();
      }
      echo CJSON::encode($res);
      Yii::app()->end();
}

We have everything, let’s use our widget in our view:

// REMEMBER, we have a hidden
// input HTML element with model's attribute_id
<?php echo $form->hiddenField($model, 'attribute_id'); ?>
<?php
// ext is a shortcut for application.extensions
$this->widget('ext.myAutoComplete', array(
    'name' => 'test_autocomplete',
    'source' => $this->createUrl('test/autocomplete'),
// attribute_value is a custom property that returns the
// name of our related object -ie return $model->related_model->name
    'value' => $model->isNewRecord ? '': $model->attribute_value,
    'options' => array(
        'minChars'=>3,
        'autoFill'=>false,
        'focus'=> 'js:function( event, ui ) {
            $( "#test_autocomplete" ).val( ui.item.name );
            return false;
        }',
        'select'=>'js:function( event, ui ) {
            $("#'.CHtml::activeId($model,'attribute_id').'")
            .val(ui.item.id);
            return false;
        }'
     ),
    'htmlOptions'=>array('class'=>'input-1', 'autocomplete'=>'off'),
    'methodChain'=>'.data( "autocomplete" )._renderItem = function( ul, item ) {
        return $( "<li></li>" )
            .data( "item.autocomplete", item )
            .append( "<a>" + item.name +  "</a>" )
            .appendTo( ul );
    };'
));
?>

Done! Just make sure that when you do submit your form, you get the value from the hidden field instead of the autocomplete element :)

Final Notes

I do not know if there are other ways of doing the same thing (apart from pure Javascript) to have the same results. If you know, with CJuiAutoComplete widget, let us know here.

Hope you find this useful.

Cheers