Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vandro/9876fc70349ef3a302dd8baaabf39953 to your computer and use it in GitHub Desktop.
Save vandro/9876fc70349ef3a302dd8baaabf39953 to your computer and use it in GitHub Desktop.
Yii: Using more than one ServerFileInput widgets on a page

Using more than one ServerFileInput widgets on a page

I need ServerFileInput widget without model and attribute, just to select file on a server or to upload file to the server, then select it. In advance I don't know how much inputs such widgets will be on a page. Therefore, I add temporary field to the model to use with this widget, then plan to clone it with jQuery.

<?php
class MyModel extends CActiveRecord
{
	public $my_attachment; // used only to generate elFinder.ServerFileInput widget
	// ...
}

Then configure connector settings in a controller:

<?php

class MyController extends Controller
{
	public function accessRules()
	{
		return [
			[
				'allow',
				'actions' => [ 'elFinderConnector', ],
				'roles'   => [ 'admin', 'authorizedUser', ],
			],
			[
				'deny',
				'users' => [ '*' ],
			],
		];
	}

	public function actionElFinderConnector()
	{
		// each user has own path named with ID, e.g.: /uploads/users/190/ for user with ID 190
		$userPath = Yii::app()->params[ 'userUploadsFolder' ] . Yii::app()->user->id . '/';
		$userRoot = Yii::getPathOfAlias( 'webroot' ) . $userPath; // full path to the user's folder

		// if directory doesn't exist, create them
		if ( !file_exists( $userRoot ) )
			mkdir( $userRoot, 0777, true );

		// initialize ElFinder
		$connector                            = new ElFinderConnectorAction( $this, $this->action->id );
		$connector->settings[ 'root' ]        = $userRoot;
		$connector->settings[ 'URL' ]         = Yii::app()->baseUrl . $userPath;
		$connector->settings[ 'rootAlias' ]   = 'Home';
		$connector->settings[ 'uploadAllow' ] = [ 'application/pdf' ]; // allow only PDF mime type
		$connector->settings[ 'uploadDeny' ]  = [ 'all' ]; // deny all other mime types
		$connector->run();
	}

}

In view I prepare container for attachments and template widget to clone later via JavaScript:

<?php 
/* @var MyController $this */
/* @var MyModel $model */
?>
<div class="attachments-container">
<!-- here will be many ServerFileInput widgets later -->
</div>

<div class="attachment-template" style="display: none;">
	<?php
	$this->widget( 'ext.elfinder.ServerFileInput', [
		'model'          => $model,
		'attribute'      => 'my_attachment',
		'connectorRoute' => 'myModule/myController/elFinderConnector',
		'twbsControls'   => true, // https://gist.github.com/umidjons/9525300
	] );
	/* Do not forget change this line in javascript code, if you change above settings!!!
	 * window.elfinderBrowse("MyModel_my_attachment", '/myModule/myController/elFinderConnector');
	 * */
	?>
</div>

I prepare function to generate new attachment widgets. In real world applications you would generate such widgets with button clicks.

jQuery(document).ready(function($){
	/**
	 * Generates new attachment widget.
	 * @param {int} idx index of attachment
	 */
	function newAttachment(idx){
		// clone from template and show widget
		var attachment=$(".attachment-template").clone(true)
			.removeClass("attachment-template").addClass(".attachment")
			.appendTo(".attachments-container").show();
		// change name, id and value attributes
		attachment.children("div").attr({name: "Attachments["+idx+"][attachment]", id: "Attachments_"+idx+"_attachmentcontainer"});
		attachment.find("input[type=text]").attr({name: "Attachments["+idx+"][attachment]", id: "Attachments_"+idx+"_attachment", value: ""});
	}
	
	// generate attachments
	for(var i=0; i<3; i++)
		newAttachment(i);
});

When user chooses a file actual URL of the chosen file written into template's input field. So we need to handle this, and copy URL into our input value.

First, I will remember current input ID as body's property:

	$("body").on("click", ".attachment input[type=button]", function(){ // save elFinder input field id as body's property
		$("body").prop("elfinderInput", $(this).closest("div").find("input[type=text]")[0].id);
		// open file browser
		window.elfinderBrowse("MyModel_my_attachment", '/myModule/myController/elFinderConnector');
	});

Default ServerFileInput implementation do not handle file select event. So we need some hack to catch file selection, also selected URL. Open ServerFileInput.php, then change this line:

<?php
	$settings[ 'editorCallback' ]        = 'js:function(url) {
    	$(\'#\'+aFieldId).attr(\'value\',url);
    }';

with this:

<?php
	$settings[ 'editorCallback' ]        = 'js:function(url) {
    	$(\'#\'+aFieldId).attr(\'value\',url);
    	$("body").trigger("elfinderFileSelected", url); // trigger custom event, also send URL to that event as parameter
    }';

Now we will catch elfinderFileSelected event and write selected URL from template's input value into current input value, which ID holded in body as a property:

	$("body").on("elfinderFileSelected", function(event, url){ // set elFinder input value to URL of selected file
		$("#"+$("body").prop("elfinderInput")).val(url);
	});

Now all things are ready to play, just handle submit request on the controller to do something with attachments, for example save their URLs in a DB:

<?php
class MyController extends Controller
{
	public function actionManageAttachments()
	{
		if ( isset( $_POST[ 'Attachments' ] ) )
		{
			foreach ( $_POST[ 'Attachments' ] as $idx => $attachment )
			{
				$attachment_model             = new MyAttachmentModel();
				$attachment_model->attributes = $attachment;
				$attachment_model->save();
			}
		}
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment