Struggling to Upload Multiples Files in Knack? Not anymore! (View - based)

Hello Folks!

Imagine that you are working on an application for a traveling agency that needs to have several files attached to a single reservation. In this case it can be for one traveler or several ones which will lead to upload information into the database such as travel insurance, passport Id, driver's license, flight and hotel Information among others. 

Since it is important to save time and accomplish tasks in a timely manner, we want to share with you this guide on how you can upload multiple files at once and be more efficient in the work you do.

Just follow these simple steps: 

1. Create new Object in Builder with at least one field of type File

![](upload://4KBQZtjAYR1VYT2E4HIsiejdJtA.png)

 

2. Create a new page with the option of requiring login, in the object select the one created in step 1 and for the views that the new page should have, select form option.

![](upload://qG1X4fgioI6SkFI5AVsYPjfks0g.png)

![](upload://uqpp76th52UkqtoMHuoZDoL6w9T.png)

![](upload://dvqiIfIY3HdcFo6U4dn9lVngMCy.png)

3. Once the new page is created, it must be indicated that it is not visible in the application menu, this is done in the settings tab.

![](upload://thOUZbjI9ca3RimyC2jqpKcSVog.png)

4. Create a new page with the options selected below:

![](upload://dEhtHsH8Dmv2SOTE0kdmJrdTLnl.png)

  1. Insert the following code in the API & Code section of the APP (Javascript Tab):

var ASYNC_URL =

  'https://cdnjs.cloudflare.com/ajax/libs/async/1.5.0/async.min.js';

// Replace your Knack APP info

var headers = {

  'X-Knack-Application-ID': 'XXXXXXXXXXX'

};

var uploadFile = function (type, filename, blob, success, error) {

  //https://api.knackhq.com/v1/applications/app_id/assets/file/upload

  success = success || function () {};

  error = error || function () {};

  type = type || 'image';

  // Required

  var fd = new FormData();

  // Add files

  if (filename) {

    fd.append('files', blob, filename);

  } else {

    fd.append('files', blob);

  }

  // Return a promise

  return $.ajax({

    type: 'POST',

    url:

      Knack.api_url +

      '/v1/applications/' +

      headers['X-Knack-Application-ID'] +

      '/assets/' +

      type +

      '/upload',

    headers,

    data: fd,

    processData: false,

    contentType: false,

    success,

    error

  });

};

var createRecord = function (fileId) {

  // All view-based requests are accessed through a scene key and view key and

  // use a URL in the following format:

  // https://api.knack.com/v1/pages/scene_key/views/view_key/records

  var user = Knack.session.user;

  if (!user) {

    // Not authenticated

    return;

  }

  return $.ajax({

    type: 'POST',

    headers: {

      'X-Knack-Application-Id': headers["X-Knack-Application-ID"],

      'Authorization': Knack.getUserToken()

    },

    // Replace scene ID and view ID from new scene created in step 2

    url: Knack.api_url + '/v1/pages/scene_XXX/views/view_XXX/records',

    data: {

      field_XXX: fileId // Replace Field ID of the new object

    }

  });

};

var dataURItoBlob = function (dataURI) {

  var byteString;

  if (dataURI.split(',')[0].indexOf('base64') >= 0) {

    byteString = atob(dataURI.split(',')[1]);

  } else {

    byteString = unescape(dataURI.split(',')[1]);

  }

  // separate out the mime component

  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

  // write the bytes of the string to a typed array

  var ia = new Uint8Array(byteString.length);

  for (var i = 0; i < byteString.length; i++) {

    ia[i] = byteString.charCodeAt(i);

  }

  return new Blob([ia], { type: mimeString });

};

// Replace your scene id

$(document).on('knack-page-render.scene_XXX', function (e, view) {

  var viewForm = 'view_XXX';  // Replace your form view id

  // Replace file field_id

  $(

    '<div class="kn-input kn-input-file">' +

      '<label for="field_XXX" class="knack-input-label"><span class="kn-input-label">Files to upload</span></label>' +

      '<input type="file" id="fileselect" name="fileselect[]" multiple="multiple" />' +

      '<div id="filedrag">or drop files here</div>' +

      '<div id="messages"></div>' +

      '</div>'

  ).insertAfter('#' + viewForm + ' .kn-form-group-1');

  var $submitInput = $('#' + viewForm + ' .kn-submit button[type=submit]');

  // Prevent submit action

  $submitInput.hide();

  LazyLoad.js([ASYNC_URL], function () {

    if (window.File && window.FileList && window.FileReader) {

      initialize();

    }

    function $id(id) {

      return document.getElementById(id);

    }

    function output(msg) {

      var m = $id('messages');

      m.innerHTML = msg + m.innerHTML;

    }

    function initialize() {

      window.files = [];

      var fileselect = $id('fileselect');

      var filedrag = $id('filedrag');

      // file select

      fileselect.addEventListener('change', FileSelectHandler, false);

      // file drop

      filedrag.addEventListener('dragover', FileDragHover, false);

      filedrag.addEventListener('dragleave', FileDragHover, false);

      filedrag.addEventListener('drop', FileSelectHandler, false);

      filedrag.style.display = 'block';

      // handle submit action

      $submitInput.on('click', function (event) {

        event.preventDefault();

        if (!files.length) {

          alert('Error, they have not been selected files');

          return;

        }

        Knack.showSpinner();

        async.each(

          files,

          function (file, next) {

            var fileReader = new FileReader();

            fileReader.onloadend = function (e) {

              var blob = dataURItoBlob(fileReader.result);

              uploadFile('file', file.name, blob, function (record) {

                createRecord(record.id)

                  .done(function () {

                    next();

                  })

                  .fail(next);

              });

            };

            fileReader.readAsDataURL(file);

          },

          function (err) {

            if (err) {

              alert('Internal error, please consult with your administrator.');

            }

            alert('All files were created successfully');

            // This reloads the page (it is optional)

            location.reload();

          }

        );

      });

      function FileDragHover(e) {

        e.stopPropagation();

        e.preventDefault();

        e.target.className = e.type == 'dragover' ? 'hover' : '';

      }

      // file selection

      function FileSelectHandler(e) {

        // cancel event and hover styling

        FileDragHover(e);

        // fetch FileList object

        var _files = e.target.files || e.dataTransfer.files;

        // process all File objects

        for (var i = 0, f; (f = _files[i]); i++) {

          files.push(f);

          showInformation(f, files.length);

        }

        handleRemoveFileEvents();

        // Allow submit

        $submitInput.show();

      }

      function showInformation(file, index) {

        output(

          '<p>' +

            'File information: <strong> ' +

            file.name +

            '</strong> type: <strong> ' +

            file.type +

            '</strong> size: <strong> ' +

            file.size +

            '</strong> bytes' +

            '&nbsp;&nbsp;&nbsp;' +

            '<a href="#" class="removeFile" data-index="' +

            index +

            '">remove</a>' +

            '</p>'

        );

      }

      function refreshInformation() {

        var m = $id('messages');

        m.innerHTML = '';

        for (var i = 0, f; (f = files[i]); i++) {

          showInformation(f, i + 1);

        }

        handleRemoveFileEvents();

      }

   function handleRemoveFileEvents() {

        $('.removeFile').on('click', function (e) {

          e.preventDefault();

          var $target = $(e.target);

          var index = $target.attr('data-index');

          files.splice(index - 1, 1);

          // Clear all

          $('.removeFile').off();

          // Capture again

          refreshInformation();

        });

      }

    }

  });

});

 

6.Insert the following code in the API & Code section of the APP (CSS Tab):

/* Replace your form view id */

#view_XXX form .kn-form-group-1 {

  display: none;

}

#filedrag

{

  min-height: 100px;

  display: none;

  font-weight: bold;

  text-align: center;

  padding: 1em 0;

  margin: 1em 0;

  color: #555;

  border: 2px dashed #555;

  border-radius: 7px;

  cursor: default;

  height: 50px;

  padding-top: 45px;

}

#filedrag.hover

{

  color: #f00;

  border-color: #f00;

  border-style: solid;

  box-shadow: inset 0 3px 4px #888;

}

#messages {

  margin-top: 15px;

}

#messages p {

  margin-bottom: 2px !important;

}

 

7. Implementation demo:

Enter this link and watch how it works: https://soluntech.knack.com/test#multifile-upload/

![](upload://gTN0wbZHbD55aYyMfQEoS2YFpP8.png)

If you found this helpful, please leave us a comment below!

Let's Make IT Happen!

Small tweak to get this working for me:

Just after the uploadFile call inside the async.each loop (inside the page-render), the returned record needed to be parsed as JSON to get the id.  There's probably other ways to do it of course.

So the uploadFile function that works for me is:

uploadFile('file', file.name, blob, function (record) {
var new_file = JSON.parse(record)
createRecord(new_file.id)
.done(function () {
next();
})
.fail(next);
});

Again thanks for sharing this very useful code Soluntech.

Excellent work - thanks for sharing.

Wow, bravo!

Hi Brad,
The files are not getting added. I tried on image files and normal files. tried soluntech code as well as your modified version! Any help what I am doing wrong!

I got it to work with the object based. Although, I cannot get it to pass the other fields on the form. This would be a big help if I get it to do that. Thank you in advance.

Greetings Soluntech,

The reference images in your post are not visible, If you could upload them again would be great.

Thanks

Thank you. Super cool script. I had to change a couple of things to get it to work.
#1 - Add ‘Authorization’: Knack.getUserToken() to headers in the first call to post the file.
#2 Wherever the async url variable was referenced I had to replace it with the actual url or it said it was not defined.

/* This is an awesome gift to the community. Thank you, Soluntech. I have made 2 changes to this script for a particular use case. Also, I am sending to Make to process rather than sending to a hidden form.

*This version captures the GUID of the record who’s detail page you are on so that you can associate the files uploaded with their parent record in the database. replace taskGuid variable with your own or leave it mis-labeled for your purpose.

*This Version limits the records uploaded to batches of 5. I found that results were inconsistent when more than 5 files were uploaded using this script. After limiting it to 5, we have had no errors */

// MULTI-FILE UPLOAD

var ASYNC_URL = ‘https://cdnjs.cloudflare.com/ajax/libs/async/1.5.0/async.min.js’;

var headers = {

‘X-Knack-Application-ID’: 'yourId, //
‘Authorization’: Knack.getUserToken()

};

var uploadFile = function (type, filename, blob, success, error) {
//https://api.knackhq.com/v1/applications/app_id/assets/file/upload
success = success || function () {};
error = error || function () {};
type = type || ‘image’;
// Required
var fd = new FormData();
// Add files
if (filename) {
fd.append(‘files’, blob, filename);
} else {
fd.append(‘files’, blob);
}
// Return a promise
return $.ajax({
type: ‘POST’,
url:
Knack.api_url +
‘/v1/applications/’ +
headers[‘X-Knack-Application-ID’] +
‘/assets/’ +
type +
‘/upload’,
headers,
data: fd,
processData: false,
contentType: false,
success,
error
});
};

var createRecord = function (fileId,taskGuid) {
// All view-based requests are accessed through a scene key and view key and
// use a URL in the following format:
// https://api.knack.com/v1/pages/scene_key/views/view_key/records
var user = Knack.session.user;
// var myUrl = window.location.href.split(“/”);
// var taskGuid = myUrl[myUrl.length - 2];

if (!user) {
// Not authenticated
return;
}
return $.ajax({
type: ‘POST’,
headers: {
‘X-Knack-Application-Id’: headers[“X-Knack-Application-ID”],
‘Authorization’: Knack.getUserToken()
},
// Replace scene ID and view ID from new scene created in step 2
url: Knack.api_url + ‘/v1/pages/scene_xxx/views/view_xxxx/records’,
data: {
field_1047: fileId, // Replace Field ID of the new object
field_1048: taskGuid // Replace Field ID of the parent record connection
}
}).done(function() {
//next function update task

 //var url = 'https://hook.us1.make.com/yourhook';

console.log('sending data to make ');

var data = {
recordId: taskGuid,
};

// Make the AJAX call
$.ajax({
url: ‘https://hook.us1.make.com/yourhook’,
type: ‘POST’,
data: data,
}).done(function(responseData) {
console.log(‘Sent Data’);
Knack.hideSpinner();
});
});

};

var dataURItoBlob = function (dataURI) {
var byteString;
if (dataURI.split(‘,’)[0].indexOf(‘base64’) >= 0) {
byteString = atob(dataURI.split(‘,’)[1]);
} else {
byteString = unescape(dataURI.split(‘,’)[1]);
}
// separate out the mime component
var mimeString = dataURI.split(‘,’)[0].split(‘:’)[1].split(‘;’)[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], { type: mimeString });
};

// Replace your scene id

$(document).on(‘knack-view-render.view_xxxx’, function (e, view) { //scene_230

var viewForm = ‘view_xxxx’; // Replace your form view id
// Replace file field_id
$(

’ +
Files to upload’ +
‘’ +
or drop files here
’ +
’ +

).insertAfter(‘#’ + viewForm + ’ .kn-form-group-1’);
var $submitInput = $(‘#’ + viewForm + ’ .kn-submit button[type=submit]');
// Prevent submit action
$submitInput.hide();
LazyLoad.js([‘https://cdnjs.cloudflare.com/ajax/libs/async/1.5.0/async.min.js’], function () {
if (window.File && window.FileList && window.FileReader) {
initialize();
}
function $id(id) {
return document.getElementById(id);
}
function output(msg) {
var m = $id(‘messages’);
m.innerHTML = msg + m.innerHTML;
}
function initialize() {
window.files = ;
var fileselect = $id(‘fileselect’);
var filedrag = $id(‘filedrag’);
// file select
fileselect.addEventListener(‘change’, FileSelectHandler, false);
// file drop
filedrag.addEventListener(‘dragover’, FileDragHover, false);
filedrag.addEventListener(‘dragleave’, FileDragHover, false);
filedrag.addEventListener(‘drop’, FileSelectHandler, false);
filedrag.style.display = ‘block’;
// handle submit action

  $submitInput.on('click', function (event) {

  


    event.preventDefault();

    console.log('file length 22222', files.length , files , taskGuid);

    var myUrl = window.location.href.split("/");
    var taskGuid = myUrl[myUrl.length - 2]; 
    
    if (!files.length) {

      alert('Error, they have not been selected files');
      return;

    } else if (files.length > 5 ) {

      alert('Please do not attempt to upload more than 5 files at a time.');
      return;

    } else {


        console.log('file inside else case ', files.length , files , taskGuid);

        Knack.showSpinner();
        async.each(
          files,
          function (file, next) {
            var fileReader = new FileReader();
            fileReader.onloadend = function (e) {
              var blob = dataURItoBlob(fileReader.result);
              uploadFile('file', file.name, blob, function (record) {
                console.log('upload file, '+file.name);
                createRecord(record.id , taskGuid)
                  .done(function () {
                    next();
                  })
                  .fail(next);
              });
            };
            fileReader.readAsDataURL(file);
          },
          function (err) {
            if (err) {
              console.log('error 237');
              alert('Internal error, please consult with your administrator.');
            }

            
          //  alert('All files were created successfully');
            // This reloads the page (it is optional)
          //  location.reload();  
            Knack.hideSpinner();
            window.location.href = "https://ps.knack.com/yourUrl/"; //redirect here

          }
        );


    }


    
  });
  function FileDragHover(e) {
    e.stopPropagation();
    e.preventDefault();
    e.target.className = e.type == 'dragover' ? 'hover' : '';
  }
  // file selection
  function FileSelectHandler(e) {
    // cancel event and hover styling
    FileDragHover(e);
    // fetch FileList object
    var _files = e.target.files || e.dataTransfer.files;
    // process all File objects
    for (var i = 0, f; (f = _files[i]); i++) {
      files.push(f);
      showInformation(f, files.length);
    }
    handleRemoveFileEvents();
    // Allow submit
    $submitInput.show();
  }
  function showInformation(file, index) {
    output(
      '<p>' +
        'File information: <strong> ' +
        file.name +
        '</strong> type: <strong> ' +
        file.type +
        '</strong> size: <strong> ' +
        file.size +
        '</strong> bytes' +
        '&nbsp;&nbsp;&nbsp;' +
        '<a href="#" class="removeFile" data-index="' +
        index +
        '">remove</a>' +
        '</p>'
    );
  }
  function refreshInformation() {
    var m = $id('messages');
    m.innerHTML = '';
    for (var i = 0, f; (f = files[i]); i++) {
      showInformation(f, i + 1);
    }
    handleRemoveFileEvents();
  }

function handleRemoveFileEvents() {
$(‘.removeFile’).on(‘click’, function (e) {
e.preventDefault();
var $target = $(e.target);
var index = $target.attr(‘data-index’);
files.splice(index - 1, 1);
// Clear all
$(‘.removeFile’).off();
// Capture again
refreshInformation();
});
}
}
});
});
/* MULTI FILE UPLOAD END */

Great solution! Does it depend on anything outside of my own knack app? For instance. Could this just stop working some day due to it relying on an external source outside my app?