Multiple file upload component

The general idea

  1. Do view model column with content override and create an external component.
  2. The component has a file input which allows user to choose multiple files.
  3. Javascript handles each file, in turn, and uploads them.
  4. To be able to receive each file and save it we create a temporary storage where each file is stored.
  5. Before a new file is uploaded, an action has to be called to create a new item where we want to save the next file, and make the variable refer to this new item.

Below there is an example on how to do this.

Model for this example

The model for the example is a project with some documents. Each document holds the file (a blob) and a filename (a string). The goal is to make it possible to choose several documents in the file browser and upload all of them.

Upload multiplefiles project document.png Upload multiplefiles document.png

Setting up the ViewModel

  • Create a view model. It should have a root, which in this case should be a project. The project holds the list / container of documents. We will need the ID of the view model class.
  • Create a variable (here named vUploadFileTarget). It should be of a type that could store a file (blob). In this example we choose the Document type. This variable will be used to temporary store each file since we are limited to one single upload at a time.

Upload multiplefiles create variable withroot.png

  • Add a generic view model column named AttachmentUploadTarget with the value of the file part of your variable (here vUploadFileTarget.File). The name here will be used in the component code. We create a view model column to be able to handle it in the component client code.
  • Add a generic view model column named AttachmentUploadTarget_FileName with the value of the string part of your variable (here vUploadFileTarget.FileName). The _FileName suffix is important since there is a MDriven "magic" that, when uploading a file, will look for a column with the same name as referenced but with a "_FileName" suffix and if such a column exists it will assign the filename to it. Thus, this part is optional, but if you skip it you will lose the name of the file.
  • Set Visible Expression to false for these columns. They are only for temporary storage.

Upload multiplefiles AttachmentUploadTarget columns.png

  • Add a generic view model column, with a name of your choice, for uploading (it will be a button).
  • Check the Content override checkbox. This is the column that will use our own component.
  • Click the TV and Attributes button, which will open a dialogue where you can set up the usage of your own external component.

Upload multiplefiles create upload button column.png

  • Add a Angular_Ext_Component tagged value. The value part is here multifiles. This is the name of your component and this name is matched to the name of your component folder, and all the component files. If you mess this name up, nothing will work. If you want to copy the component code from this example (below) you should write multifiles. If you want to write your own component, please choose whatever name you like, and if you're up for a real adventure you can even try to use uppercase letters and camel case.

Upload multiplefiles ExtComponent.png

  • Create an action for the view model with the name AddDocumentToProject. This name is referenced from the component code. If you choose another name, you will have to reference that name instead in the component.

Upload multiplefiles action code.png

  • Document is the name of the type that holds file and filename.
  • vCurrent_Project references the current project, and Project is the name of the type.
  • vUploadFileTarget is the name of the variable in the view model.

This code creates a new document and assigns the variable to this new document. This is necessary to prevent the new file from overriding the last file. After the last uploaded file, vUploadFileTarget will contain the last file, until a new multifile upload is initiated.

Action "Execute expression" code:

let x = Document.Create in (
vCurrent_Project.Document.Add(x);
vUploadFileTarget:= x;
x
)

Creating the component

  • In your model directory (named something like "xyz123_MDrivenServerA0_AssetsTK") add a directory named exactly EXT_Components (unless it already exist).
  • In the EXT_Components directory, create a new directory with the name of your component. In this example: multifiles
  • Create one multifiles.js and one multifiles.cshtml in that directory (change "multifiles" to your own component name if you have another).

The cshtml file code below is generic except for the class Projects_UploadMultipleDocuments which is <view model name>_<column name for upload button>.

<!-- multifiles.cshtml -->
<div class="Projects_UploadMultipleDocuments <!-- This class will match the view model name and component column. CHANGE! -->
            tk-component ctButton tk-button NoLabel" 
            multifiles> <!-- This defines the component. -->
    <div class="tk-file-upload__inner">
        <input type="file"
               id="addAttachments"
               class="tk-file-upload__native"
               accept=".pdf,.docx,.doc,.odt" <!-- The file types to accept -->
               readmultifiles <!-- This is the directive -->
               multiple /> <!-- This makes it possible to add several files -->
        <label for="addAttachments" title="[ViewModelColumnLabel]" class="tk-file-upload__interactive ripple-effect">
            [ViewModelColumnLabel]
            <div class="tk-file-upload__progress"
                 style="width: {{ViewModelRoot.ViewData['FileUploadPercent__undefined.AttachmentUploadTarget']}}%;"></div>
        </label>
    </div>
 <span class="tk-file-upload__name">{{ViewModelRoot.ViewData['FileUploadName__undefined.AttachmentUploadTarget']}}</span>
</div>

The javascript code below is generic except for

  • AddDocumentToProject which is the action name of the action that creates new documents for each project.
  • AttachmentUploadTarget which is the name of the view model column storing the file blob.
  • All occurences of multifiles, which is the name of the component.
//
// multifiles.js
//
function UploadFiles(files, scope, index = 0) {
    const file = files[index];

    // scope.StreamingViewModelClient.ExecuteAfterFullRoundtrip is a
    // synchronous call to server. Since calls are executed in order at the
    // server this will ensure that the previous asynchronous call has been
    // executed.
    scope.StreamingViewModelClient.ExecuteAfterFullRoundtrip('waitForReadyToExecute' + index, null, () => {
        // Creates a new attachment which next upload will target
        scope.data.Execute('AddDocumentToProject'); // CHANGE IF NOT APPLICABLE - this is the name of the action

        scope.StreamingViewModelClient.ExecuteAfterFullRoundtrip('waitForSkapaBilagaToFinish' + index, null, () => {
            // Uploads file to specified target
            scope.StreamingViewModelClient.UploadFile(file.Target, file.VMClassId, file.Data, file.FileName);

            scope.StreamingViewModelClient.ExecuteAfterFullRoundtrip('waitingForUploadFileToFinish' + index, null, () => {
                // Start next upload if there are any more uploads
                if (index < files.length - 1) {
                    UploadFiles(files, scope, index + 1);
                }
            });
        });
    });
}

function InstallTheDirectiveFor_multifiles(streamingAppController) {
    streamingAppController.directive('readmultifiles', ['$document', function ($document) {
            return {
				link: function (scope, element, attr) {
                    element.bind('change', function () {
                        const maxSizeInBytes = 100000000;
                        var nrExpectedFiles = element[0].files.length;
                        var filesToUpload = [];
                        angular.forEach(element[0].files, function (item) {
                            if (maxSizeInBytes === 0 || item.size <= maxSizeInBytes) {
                                const filereader = new FileReader();
                                const filename = item.name;
                                const vmclassid = scope.data.VMClassId.asString;
                                const viewModelName = scope.data.ViewModelName;
                                const target = viewModelName + '.AttachmentUploadTarget'; // CHANGE IF NOT APPLICABLE

                                filereader.onloadend = function (loadendevent) {
                                    var data = loadendevent.target.result;
                                    var fileToUpload = {
                                        Target: target,
                                        VMClassId: vmclassid,
                                        Data: data,
                                        FileName: filename
                                    };
                                    filesToUpload.push(fileToUpload);
                                    if (filesToUpload.length == nrExpectedFiles) {
                                        UploadFiles(filesToUpload, scope);
                                    }
                                };

                                filereader.onabort = function (abortevent) {
                                    filesToUpload = [];
                                    nrExpectedFiles = -1;
                                };

                                filereader.onerror = function (erroevent) {
                                    nrExpectedFiles--;
                                    if (filesToUpload.length == nrExpectedFiles) {
                                        UploadFiles(filesToUpload, scope);
                                    }
                                };

                                filereader.readAsArrayBuffer(item);    
                            } else {
                                window.alert(item.name + " is larger than " + maxSizeInBytes + " bytes, can't upload.");
                            }
						});
					});
				}
			}	
        }]);
		
    console.trace("multifiles component Loaded");
}
InstallTheDirectiveFor_multifiles(angular.module(MDrivenAngularAppModule));
This page was edited 37 days ago on 03/26/2024. What links here