Quellcode durchsuchen

Portal :
- Support for attachments in forms
- Added a loader on LinkedSet fields while form is retrieving information on server when adding objects

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4014 a333f486-631f-4898-b8df-5754b55c2be0

glajarige vor 9 Jahren
Ursprung
Commit
c28e9fa033

+ 2 - 5
js/field_set.js

@@ -53,15 +53,12 @@ $(function()
 			
 			this.element
 			.bind('field_change', function(oEvent, oData){
-				//console.log('field_set: field_change');
 				me._onFieldChange(oEvent, oData);
 			})
 			.bind('update_form', function(oEvent, oData){
-				//console.log('field_set: update_form');
 				me._onUpdateForm(oEvent, oData);
 			})
 			.bind('get_current_values', function(oEvent, oData){
-				//console.log('field_set: get_current_values');
 				return me._onGetCurrentValues(oEvent, oData);
 			})
 			.bind('validate', function(oEvent, oData){
@@ -69,7 +66,7 @@ $(function()
 				{
 					oData = {};
 				}
-				//console.log('field_set: validate');
+				
 				return me._onValidate(oEvent, oData);
 			});
 
@@ -166,7 +163,7 @@ $(function()
 
 			// Validate the field
 			var oResult = this.getField(oData.name).triggerHandler('validate', {touched_fields_only: true});
-			if (!oResult.is_valid)
+			if ( (oResult !== undefined) && !oResult.is_valid)
 			{
 				this.options.is_valid = false;
 			}

+ 2 - 0
sources/autoload.php

@@ -23,6 +23,7 @@
 require_once APPROOT . 'sources/form/form.class.inc.php';
 require_once APPROOT . 'sources/form/formmanager.class.inc.php';
 require_once APPROOT . 'sources/form/field/field.class.inc.php';
+require_once APPROOT . 'sources/form/field/fileuploadfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/subformfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/textfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/hiddenfield.class.inc.php';
@@ -52,3 +53,4 @@ require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssimplefieldre
 require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php';
 require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bslinkedsetfieldrenderer.class.inc.php';
 require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssubformfieldrenderer.class.inc.php';
+require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php';

+ 115 - 0
sources/form/field/fileuploadfield.class.inc.php

@@ -0,0 +1,115 @@
+<?php
+
+// Copyright (C) 2010-2016 Combodo SARL
+//
+//   This file is part of iTop.
+//
+//   iTop is free software; you can redistribute it and/or modify	
+//   it under the terms of the GNU Affero General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   iTop is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU Affero General Public License for more details.
+//
+//   You should have received a copy of the GNU Affero General Public License
+//   along with iTop. If not, see <http://www.gnu.org/licenses/>
+
+namespace Combodo\iTop\Form\Field;
+
+use \Combodo\iTop\Form\Field\Field;
+
+/**
+ * Description of FileUploadField
+ *
+ * @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
+ */
+class FileUploadField extends Field
+{
+	const DEFAULT_ALLOW_DELETE = true;
+
+	protected $sTransactionId;
+	protected $oObject;
+	protected $sUploadEndpoint;
+	protected $sDownloadEndpoint;
+	protected $bAllowDelete;
+
+	public function __construct($sId, \Closure $onFinalizeCallback = null)
+	{
+		$this->sTransactionId = null;
+		$this->oObject = null;
+		$this->sUploadEndpoint = null;
+		$this->sDownloadEndpoint = null;
+		$this->bAllowDelete = static::DEFAULT_ALLOW_DELETE;
+
+		parent::__construct($sId, $onFinalizeCallback);
+	}
+
+	/**
+	 * Returns the transaction id for the field.
+	 *
+	 * @return string
+	 */
+	public function GetTransactionId()
+	{
+		return $this->sTransactionId;
+	}
+
+	/**
+	 *
+	 * @param string $sTransactionId
+	 * @return \Combodo\iTop\Form\Field\FileUploadField
+	 */
+	public function SetTransactionId($sTransactionId)
+	{
+		$this->sTransactionId = $sTransactionId;
+		return $this;
+	}
+
+	public function GetObject()
+	{
+		return $this->oObject;
+	}
+
+	public function SetObject($oObject)
+	{
+		$this->oObject = $oObject;
+		return $this;
+	}
+
+	public function GetUploadEndpoint()
+	{
+		return $this->sUploadEndpoint;
+	}
+
+	public function SetUploadEndpoint($sUploadEndpoint)
+	{
+		$this->sUploadEndpoint = $sUploadEndpoint;
+		return $this;
+	}
+
+	public function GetDownloadEndpoint()
+	{
+		return $this->sDownloadEndpoint;
+	}
+
+	public function SetDownloadEndpoint($sDownloadEndpoint)
+	{
+		$this->sDownloadEndpoint = $sDownloadEndpoint;
+		return $this;
+	}
+
+	public function GetAllowDelete()
+	{
+		return $this->bAllowDelete;
+	}
+
+	public function SetAllowDelete($bAllowDelete)
+	{
+		$this->bAllowDelete = (boolean) $bAllowDelete;
+		return $this;
+	}
+
+}

+ 1 - 0
sources/renderer/bootstrap/bsformrenderer.class.inc.php

@@ -52,6 +52,7 @@ class BsFormRenderer extends FormRenderer
 		$this->AddSupportedField('SubFormField', 'BsSubFormFieldRenderer');
 		$this->AddSupportedField('SelectObjectField', 'BsSelectObjectFieldRenderer');
 		$this->AddSupportedField('LinkedSetField', 'BsLinkedSetFieldRenderer');
+		$this->AddSupportedField('FileUploadField', 'BsFileUploadFieldRenderer');
 	}
 
 }

+ 237 - 0
sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php

@@ -0,0 +1,237 @@
+<?php
+
+// Copyright (C) 2010-2016 Combodo SARL
+//
+//   This file is part of iTop.
+//
+//   iTop is free software; you can redistribute it and/or modify	
+//   it under the terms of the GNU Affero General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   iTop is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU Affero General Public License for more details.
+//
+//   You should have received a copy of the GNU Affero General Public License
+//   along with iTop. If not, see <http://www.gnu.org/licenses/>
+
+namespace Combodo\iTop\Renderer\Bootstrap\FieldRenderer;
+
+use \utils;
+use \Dict;
+use \UserRights;
+use \InlineImage;
+use \DBObjectSet;
+use \DBObjectSearch;
+use \MetaModel;
+use \Combodo\iTop\Renderer\FieldRenderer;
+use \Combodo\iTop\Renderer\RenderingOutput;
+use \Combodo\iTop\Form\Field\LinkedSetField;
+
+/**
+ * Description of BsFileUploadFieldRenderer
+ *
+ * @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
+ */
+class BsFileUploadFieldRenderer extends FieldRenderer
+{
+
+	/**
+	 * Returns a RenderingOutput for the FieldRenderer's Field
+	 *
+	 * @return \Combodo\iTop\Renderer\RenderingOutput
+	 */
+	public function Render()
+	{
+		$oOutput = new RenderingOutput();
+
+		$sObjectClass = get_class($this->oField->GetObject());
+		$sIsDeleteAllowed = ($this->oField->GetAllowDelete()) ? 'true' : 'false';
+		$sDeleteBtn = Dict::S('Portal:Button:Delete');
+		$sTempId = session_id() . '_' . $this->oField->GetTransactionId();
+		$sUploadDropZoneLabel = Dict::S('Portal:Attachments:DropZone:Message');
+
+		// Starting field container
+		$oOutput->AddHtml('<div class="form-group">');
+		// Field label
+		if ($this->oField->GetLabel() !== '')
+		{
+			$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
+		}
+		// Field feedback
+		$oOutput->AddHtml('<div class="help-block"></div>');
+		// Starting files container
+		$oOutput->AddHtml('<div class="fileupload_field_content">');
+		// Files list
+		$oOutput->AddHtml('<div class="attachments_container row">');
+		$this->PrepareExistingFiles($oOutput);
+		$oOutput->Addhtml('</div>');
+		// TODO : Add max upload size when itop attachment has been refactored
+		$oOutput->AddHtml('<div class="upload_container row">' . Dict::S('Attachments:AddAttachment') . '<input type="file" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" /><span class="loader glyphicon glyphicon-refresh"></span></div>');
+		// Ending files container
+		$oOutput->AddHtml('</div>');
+		// Ending field container
+		$oOutput->AddHtml('</div>');
+		
+		// JS for file upload
+		// Note : This is based on itop-attachement/main.attachments.php
+		$oOutput->AddJs(
+<<<EOF
+			var RemoveAttachment = function(sAttId)
+			{
+				$('#attachment_' + sAttId).attr('name', 'removed_attachments[]');
+				$('#display_attachment_' + sAttId).hide();
+			};
+
+			$('#{$this->oField->GetGlobalId()}').fileupload({
+				url: '{$this->oField->GetUploadEndpoint()}',
+				formData: { operation: 'add', temp_id: '{$sTempId}', object_class: '{$sObjectClass}', 'field_name': '{$this->oField->GetId()}' },
+				dataType: 'json',
+				pasteZone: null, // Don't accept files via Chrome's copy/paste
+				done: function (e, data) {
+					if(data.result.error !== undefined)
+					{
+						console.log(data.result.error);
+					}
+					else
+					{
+						var sDownloadLink = '{$this->oField->GetDownloadEndpoint()}'.replace(/-sAttId-/, data.result.att_id);
+
+						$(this).closest('.fileupload_field_content').find('.attachments_container').append(
+							'<div class="attachment col-xs-6 col-sm-3 col-md-2" id="display_attachment_'+data.result.att_id+'">'+
+							'	<a data-preview="'+data.result.preview+'" href="'+sDownloadLink+'" title="'+data.result.msg+'">'+
+							'		<div class="attachment_icon"><img src="'+data.result.icon+'"></div>'+
+							'		<div class="attachment_name">'+data.result.msg+'</div>'+
+							'		<input id="attachment_'+data.result.att_id+'" type="hidden" name="attachments[]" value="'+data.result.att_id+'"/>'+
+							'	</a>'+
+							'	<input type="button" class="btn btn-xs btn-danger hidden" value="{$sDeleteBtn}"/>'+
+							'</div>'
+						);
+
+						$('#display_attachment_'+data.result.att_id+' :button').click(function(oEvent){
+							oEvent.preventDefault();
+							RemoveAttachment(data.result.att_id);
+						});
+
+						$('#display_attachment_'+data.result.att_id).hover( function(){
+							$(this).children(':button').toggleClass('hidden');
+						});
+					}
+				},
+				start: function() {
+					// Scrolling to dropzone so the user can see that attachments are uploaded
+					$(this)[0].scrollIntoView();
+					// Showing loader
+					$(this).closest('.upload_container').find('.loader').css('visibility', 'visible');
+				},
+				stop: function() {
+					// Hiding the loader
+					$(this).closest('.upload_container').find('.loader').css('visibility', 'hidden');
+					// Adding this field to the touched fields of the field set so the cancel event is called if necessary
+					$(this).closest(".field_set").trigger("field_change", {
+						id: '{$this->oField->GetGlobalId()}',
+						name: '{$this->oField->GetId()}'
+					});
+				}
+			});
+			
+			$('.attachments_container .attachment :button').click(function(oEvent){
+				oEvent.preventDefault();
+				RemoveAttachment($(this).closest('.attachment').find(':input[name="attachments[]"]').val());
+			});
+
+			if($sIsDeleteAllowed)
+			{
+				$('.attachment').hover( function(){
+					$(this).find(':button').toggleClass('hidden');
+				});
+			}
+
+			// Handles a drag / drop overlay
+			if($('#drag_overlay').length === 0)
+			{
+				$('body').append( $('<div id="drag_overlay" class="global_overlay"><div class="overlay_content"><div class="content_uploader"><div class="icon glyphicon glyphicon-cloud-upload"></div><div class="message">{$sUploadDropZoneLabel}</div></div></div></div>') );
+			}
+
+			// Handles highlighting of the drop zone
+			// Note : This is inspired by itop-attachments/main.attachments.php
+			$(document).on('dragover', function(oEvent){
+				var bFiles = false;
+				if (oEvent.dataTransfer && oEvent.dataTransfer.types)
+				{
+					for (var i = 0; i < oEvent.dataTransfer.types.length; i++)
+					{
+						if (oEvent.dataTransfer.types[i] == "application/x-moz-nativeimage")
+						{
+							bFiles = false; // mozilla contains "Files" in the types list when dragging images inside the page, but it also contains "application/x-moz-nativeimage" before
+							break;
+						}
+
+						if (oEvent.dataTransfer.types[i] == "Files")
+						{
+							bFiles = true;
+							break;
+						}
+					}
+				}
+
+				if (!bFiles) return; // Not dragging files
+
+				var oDropZone = $('#drag_overlay');
+				var oTimeout = window.dropZoneTimeout;
+				// This is to detect when there is no drag over because there is no "drag out" event
+				if (!oTimeout) {
+					oDropZone.removeClass('drag_out').addClass('drag_in');
+				} else {
+					clearTimeout(oTimeout);
+				}
+				window.dropZoneTimeout = setTimeout(function () {
+					window.dropZoneTimeout = null;
+					oDropZone.removeClass('drag_in').addClass('drag_out');
+				}, 200);
+			});
+
+EOF
+		);
+
+		return $oOutput;
+	}
+
+	/**
+	 * 
+	 * @param RenderingOutput $oOutput
+	 */
+	protected function PrepareExistingFiles(RenderingOutput &$oOutput)
+	{
+		$sObjectClass = get_class($this->oField->GetObject());
+		$sDeleteBtn = Dict::S('Portal:Button:Delete');
+
+		$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
+		$oSet = new DBObjectSet($oSearch, array(), array('class' => $sObjectClass, 'item_id' => $this->oField->GetObject()->GetKey()));
+		while ($oAttachment = $oSet->Fetch())
+		{
+			$iAttId = $oAttachment->GetKey();
+			$oDoc = $oAttachment->Get('contents');
+			$sFileName = htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8');
+			$sIcon = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-attachments/icons/image.png';
+			$sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
+			$sDownloadLink = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDownloadEndpoint());
+
+			$oOutput->Addhtml(
+<<<EOF
+				<div class="attachment col-xs-6 col-sm-3 col-md-2" id="display_attachment_{$iAttId}">
+					<a data-preview="{$sPreview}" href="{$sDownloadLink}" title="{$sFileName}">
+						<div class="attachment_icon"><img src="{$sIcon}"></div>
+						<div class="attachment_name">{$sFileName}</div>
+						<input id="attachment_{$iAttId}" type="hidden" name="attachments[]" value="{$iAttId}"/>
+					</a>
+					<input id="btn_remove_{$iAttId}" type="button" class="btn btn-xs btn-danger hidden" value="{$sDeleteBtn}"/>
+				</div>
+EOF
+			);
+		}
+	}
+
+}

+ 8 - 1
sources/renderer/bootstrap/fieldrenderer/bslinkedsetfieldrenderer.class.inc.php

@@ -30,7 +30,7 @@ use \Combodo\iTop\Renderer\RenderingOutput;
 use \Combodo\iTop\Form\Field\LinkedSetField;
 
 /**
- * Description of BsSelectObjectFieldRenderer
+ * Description of BsLinkedSetFieldRenderer
  *
  * @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
  */
@@ -182,6 +182,9 @@ EOF
 						// When we have data (meaning that we picked objects from search)
 						if(oData !== undefined && Object.keys(oData.values).length > 0)
 						{
+							// Showing loader while retrieving informations
+							$('#page_overlay').fadeIn(200);
+
 							// Retrieving new rows ids
 							var aObjectIds = Object.keys(oData.values);
 
@@ -222,6 +225,10 @@ EOF
 								}
 
 								$('#{$this->oField->GetGlobalId()}').val(JSON.stringify(aObjectIds));
+							})
+							.always(function(oData){
+								// Hiding loader
+								$('#page_overlay').fadeOut(200);
 							});
 						}
 						// We come from a button