Agile Web Application Development with Yii 1.1 and PHP5Yii FrameworkRequested by a reader, I promised to him to write an article describing how to have more than one instance of the great Yii extension SWFUpload. Even though he has fixed the issue by himself, I will describe how to do it for others to know just in case they face the same challenge.

Step One Fixing Internal Extension Code

If we look at the extension code we will see that the problem is, in fact, due to the nature of it. Lets look at the code, I will comment where the problem relies:

public function run()
{
    $assets = dirname(__FILE__).'/swfupload';
    $baseUrl = Yii::app()->assetManager->publish($assets);
    $cs = Yii::app()->getClientScript();
    // Registering required SWFUpload files
    $cs->registerScript(__CLASS__.'swfuv',"var swfuPath='" . $baseUrl . "';", CClientScript::POS_HEAD);
    $cs->registerScriptFile($baseUrl . '/swfupload.js', CClientScript::POS_HEAD);
    $cs->registerScriptFile($baseUrl . '/handlers.js', CClientScript::POS_END);
    $cs->registerCssFile($baseUrl . '/swfupload.css');
    // ensure PHPSESSIONID for SWFUpload session ID problem
    $postParams = array('PHPSESSID'=>session_id());
    // if POST parameters have been set by user, merge to the configuration
    // post_params parameter of SWFUpload
    if(isset($this->postParams))
       $postParams = array_merge($postParams, $this->postParams);
    // default config options
    $config = array(
	'post_params'=> $postParams,
	'flash_url'=> $baseUrl. '/swfupload.swf',
	'button_image_url'=>
               $baseUrl .'/images/SmallSpyGlassWithTransperancy_17x18.png',
	);
     $config = array_merge($config, $this->config);
     $config = CJavaScript::encode($config);
    // here is where the problem relies, we only register one script
    // by one variable javascript name, we need to modify this.
     $cs->registerScript(__CLASS__, "
	var swfu;
	swfu = new SWFUpload($config);
     ");
}

Now that we have found where the issue is, we just need to change a bit the code to be able to work with more than one SWFUpload buttons at once. First, we are going to add a static counter to the extension, and it will be the one that will make sure that each instance will have its own name. And then, we will provide a unique name to the script that is flushed to the response. Also, even though Yii handles it very well and makes sure no registered script under the same name is twice register, we will check if required files are already registered by CClientScript -by using the isScriptRegistered, isScriptFileRegistered and isCssFileRegistered functions. Following my proposed solution:

class CSwfUpload extends CWidget
{
    public $postParams=array();
    public $config=array();
    // static counter to avoid instances
    // name conflict
    private static $_counter=0;
    public function run()
    {
	$assets = dirname(__FILE__).'/swfupload';
        $baseUrl = Yii::app()->assetManager->publish($assets);
        $cs = Yii::app()->getClientScript();
        // isScriptRegistered makes sure we dont register the script twice
        if(!$cs->isScriptFileRegistered(__CLASS__.'swfuv', CClientScript::POS_HEAD))
       		$cs->registerScript(__CLASS__.'swfuv',"var swfuPath='" . $baseUrl . "';", CClientScript::POS_HEAD);
        if(!$cs->isScriptFileRegistered($baseUrl . '/swfupload.js', CClientScript::POS_HEAD))
		$cs->registerScriptFile($baseUrl . '/swfupload.js', CClientScript::POS_HEAD);
	if(!$cs->isScriptFileRegistered($baseUrl . '/handlers.js', CClientScript::POS_END))
		$cs->registerScriptFile($baseUrl . '/handlers.js', CClientScript::POS_END);
	if(!$cs->isCssFileRegistered($baseUrl . '/swfupload.css'))
		$cs->registerCssFile($baseUrl . '/swfupload.css');
	$postParams = array('PHPSESSID'=>session_id());
	if(isset($this->postParams))
	{
		$postParams = array_merge($postParams, $this->postParams);
	}
	$config = array(
		'post_params'=> $postParams,
		'flash_url'=> $baseUrl. '/swfupload.swf',
		'button_image_url'=>
                     $baseUrl .'/images/SmallSpyGlassWithTransperancy_17x18.png',
	);
	$config = array_merge($config, $this->config);
	$config = CJavaScript::encode($config);
        // update our static variable in order to create
        // a unique variable javascript name
	self::$_counter++;
        // we could just use the same static variable to
        // register the script name but I thought this
        // is not bad solution to show
        // PLEASE SEE THE USE OF THE STATIC VARIABLE
        // TO CREATE THE SWFUPLOAD OBJECT
	$cs->registerScript(__CLASS__.sha1(self::$_counter), "
		var swfu".self::$_counter." = new SWFUpload($config);
		");
        }
}

Step Two Modifying our View

Now, last but not least, for our modified extension to work we need to setup our view with the correct settings. We will have to create two widgets, and they will have to point to two different layers to render the SWFUpload object -please refer to the article on How to use SWFUpload Extension on this blog for instructions with one instance.

Following, an example on how to declare those two instances and the HTML:

 // where are we going to post?
 $uurl = $this->createUrl('picture/upload',array('newsId'=>$newsId));
 $options = array(
	'use_query_string'=>false,
        'upload_url'=> CHtml::normalizeUrl($uurl),
        'file_size_limit'=>'2 gb',
        'file_types'=>'*.jpg;*.png;*.gif',
        'file_types_description'=>'Imagenes',
        'file_upload_limit'=>0,
        'file_queue_error_handler'=>'js:fileQueueError',
        'file_dialog_complete_handler'=>'js:fileDialogComplete',
        'upload_progress_handler'=>'js:uploadProgress',
        'upload_error_handler'=>'js:uploadError',
        'upload_success_handler'=>'js:uploadSuccess',
        'upload_complete_handler'=>'js:uploadComplete',
        // where are we going to inform our upload progress
        // for this SWFUpload object?
        'custom_settings'=>array('upload_target'=>'divFileProgressContainer'),
       // where are we going to render the SWFUpload button?
        'button_placeholder_id'=>'swfupload',
        'button_width'=>230,
        'button_height'=>20,
        'button_text'=>'<span class="button">Select Images (2 MB Max)</span>',
        'button_text_style'=>'.button { font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; font-size: 11pt; text-align: center; }',
        'button_text_top_padding'=>0,
        'button_text_left_padding'=>0,
        'button_window_mode'=>'js:SWFUpload.WINDOW_MODE.TRANSPARENT',
        'button_cursor'=>'js:SWFUpload.CURSOR.HAND',
        );
    // creeate one widget!
    $this->widget('application.widgets.CSwfUpload', array(
       'postParams'=>array('newsId'=>$newsId),
        'config'=>$options,
    )
);
 // change options for our next uploader
 $options['custom_settings']=array('upload_target'=>'divFileProgressContainer2');
 $options['button_placeholder_id']='swfupload2';
 // create new widget (I dont change the post params
 // nor the post URL that handles submission because
 // it is not important for the sake of the example
 $this->widget('application.widgets.CSwfUpload', array(
    'postParams'=>array('newsId'=>$newsId),
    'config'=>$options,
    )
);

Here our HTML, please refer to the code above to see how the layers are connected to the options of the plugin.

<div class="container">
<div id="content">
<h2>Image Uploader</h2>
<p>Click at the following button to upload the images of the news gallery.
All images will be resized automatically.</p>
<form>
<div class="form">
	<div class="row">
	    <div class="swfupload"  style="display: inline; border: solid 1px #7FAAFF; background-color: #C5D9FF; padding: 2px;">
                 <span id="swfupload"></span>
            </div>
	    <div id="divFileProgressContainer" style="height: 75px;"></div>
	</div>
	<div class="row">
            <div class="swfupload"  style="display: inline; border: solid 1px #7FAAFF; background-color: #C5D9FF; padding: 2px;">
                 <span id="swfupload2"></span></div>
            <div id="divFileProgressContainer2" style="height: 75px;"></div>
	</div>
<div id="thumbnails"></div>
</div>
</form>
</div><!-- content -->
</div>

Addedum

I highly recommend you to have a look at the code of every extension you download. They are a good place to learn and to also help us improve our ‘problem solving’ thinking. I hope that you enjoyed this article as much as I had writing it.



Tweet this!Tweet this!