Ilaria  Dugg

Ilaria Dugg

1565338451

How to Drag and Drop File Uploads with Vue.js and Axios

When it comes to uploading files for the web, dragging and dropping files from a directory is one of the most convenient ways to accomplish this task. Almost any modern web application allows you to do this. Up until this point, we have all the different tricks to upload files with VueJS and Axios. We have standard file uploads where users can do a single file, multiple files, and even edit the files before submitted: Uploading Files With VueJS and Axios – Server Side Up, we have file previews: Preview File Uploads with Axios and VueJS, and we have a status bar of the upload process: File Upload Progress Indicator with Axios and VueJS .

In this tutorial we are going to combine all of these tricks to make the ultimate file uploader with VueJS and Axios. We will allow users to select any amount of files, remove the ones they don’t, show a preview, and show a status bar for uploading the files. The user will be able to select which files they want through dragging and dropping or selecting through the standard uploader.

A lot of the drag and drop functionality comes from Osvaldas Valutis and his guest post on CSS tricks: Drag and Drop File Uploading | CSS-Tricks. Mad props to Osvaldas, the tutorial was the best I’ve seen. I’ve adapted what we need to be inside a Vue component and not with jQuery and added some of the bells and whistles from the last couple articles.

This tutorial will flow in steps, we will:

1. Build our drag and drop base.

2. Add previews for the files selected if they are images

3. Allow users to remove the files they don’t want any more

4. Upload the selected files

5. Add a progress bar

6. Take a short derivative for the direct to upload process. This will upload the files when they are dropped onto the drop base.

Lots of cool stuff in this tutorial, but it can get a little complicated at times so reach out if you need a hand during any of the steps!

Building Your Drag And Drop Base

This is the guts of the tutorial, but also where we should start. The key to having a drag and drop file uploader is to have an element where you can drag and drop files.

First, we need to create an empty Vue Component. In this component add the following component stub:

<style>
  form{
    display: block;
    height: 400px;
    width: 400px;
    background: #ccc;
    margin: auto;
    margin-top: 40px;
    text-align: center;
    line-height: 400px;
      border-radius: 4px;
  }
</style>

<template>
<div id=“file-drag-drop”>
<form ref=“fileform”>
<span class=“drop-files”>Drop the files here!</span>
</form>
</div>
</template>

<script>
export default {

}
</script>

In here is where we will build all of our functionality. I added a real basic style for the form which just centers it in the screen and added some text to instruct the users where to drag and drop the files. Osvaldas has a beautiful UX in his tutorial here: Drag and Drop File Uploading | CSS-Tricks. I’ll be focusing just on porting some of the functionality to VueJS and Axios. Feel free to style the elements anyway you’d like!

The only real thing to point about about the template is the ref attribute on the <form> tag. This allows us to access the element from within our component code.

Confirm that browser is drag and drop capable

The first piece of functionality we will add to our component is a check to see if the drag and drop functionality is supported or not. Older browsers don’t support drag and drop so we will allow for a backup plan with the users clicking a button.

First, let’s add a data field to our component with a flag to see if the browser is drag and drop capable:

/*
Variables used by the drag and drop component
/
data(){
return {
dragAndDropCapable: false,
files: []
}
},

This flag will determine if we show the text to allow drag and drop and if we even bind the events to the elements. We also added an array of files that will store the files the user selected when they dragged and dropped over the element.

Next, we will want to add a method that determines whether the browser is drag and drop capable. Add the following method to your methods object in the Vue component:

 /
 Determines if the drag and drop functionality is in the
 window
/
determineDragAndDropCapable(){
 /

  Create a test element to see if certain events
  are present that let us do drag and drop.
 */
 var div = document.createElement(‘div’);

 /*
  Check to see if the draggable event is in the element
  or the ondragstart and ondrop events are in the element. If
  they are, then we have what we need for dragging and dropping files.

  We also check to see if the window has FormData and FileReader objects
  present so we can do our AJAX uploading
 /
 return ( ( ‘draggable’ in div )
     || ( ‘ondragstart’ in div && ‘ondrop’ in div ) )
     && ‘FormData’ in window
     && ‘FileReader’ in window;
}

To step through this, we first create a test div element where we can see if certain events are available. These events allow us to listen to the drag and drop events from the files.

We also return if the FormData (FormData – Web APIs | MDN) and FileReader (FileReader – Web APIs | MDN) objects are present in the window for full functionality. If these all exist, we will return true from the method and continue the initialization.

Initialize our component through the mounted() lifecycle hook

We will be using the functionality from the determineDragAndDropCapable() method in the mounted() lifecycle hook to determine whether or not we should add the event listeners.

Let’s first add our lifecycle hook and initializers like so:

mounted(){
 /

  Determine if drag and drop functionality is capable in the browser
 */
 this.dragAndDropCapable = this.determineDragAndDropCapable();

 /*
  If drag and drop capable, then we continue to bind events to our elements.
 /
 if( this.dragAndDropCapable ){
  /

   Listen to all of the drag events and bind an event listener to each
   for the fileform.
  /
  [‘drag’, ‘dragstart’, ‘dragend’, ‘dragover’, ‘dragenter’, ‘dragleave’, ‘drop’].forEach( function( evt ) {
   /

    For each event add an event listener that prevents the default action
    (opening the file in the browser) and stop the propagation of the event (so
    no other elements open the file in the browser)
   */
   this.$refs.fileform.addEventListener(evt, function(e){
    e.preventDefault();
    e.stopPropagation();
   }.bind(this), false);
  }.bind(this));

  /*
   Add an event listener for drop to the form
  /
  this.$refs.fileform.addEventListener(‘drop’, function(e){
   /

    Capture the files from the drop event and add them to our local files
    array.
   /
   for( let i = 0; i < e.dataTransfer.files.length; i++ ){
    this.files.push( e.dataTransfer.files[i] );
   }
  }.bind(this));
 }
},

The most important reason to use the mounted() hook is it runs after the template is injected into the HTML. This allows us to access the reference to the form that we created when we created our template.

First, we set our local dragAndDropCapable flag to what is returned from our method:

/
Determine if drag and drop functionality is capable in the browser
/
this.dragAndDropCapable = this.determineDragAndDropCapable();

This determines if we should continue binding the events we are looking for on the form field or not. No sense binding events that don’t exist!

After we determine that drag and drop functionality is capable, we iterate over all of the drag related events and bind them to our form:

/
 Listen to all of the drag events and bind an event listener to each
 for the fileform.
/
[‘drag’, ‘dragstart’, ‘dragend’, ‘dragover’, ‘dragenter’, ‘dragleave’, ‘drop’].forEach( function( evt ) {
 /

  For each event add an event listener that prevents the default action
  (opening the file in the browser) and stop the propagation of the event (so
  no other elements open the file in the browser)
 /
 this.$refs.fileform.addEventListener(evt, function(e){
  e.preventDefault();
  e.stopPropagation();
 }.bind(this), false);
}.bind(this));

What we do here is iterate over an array of the drag related events. We then bind an event listener to each event on the form we access with our global $refs array. What we simply do with the events is prevent the default action which is to open the files dragged into the browser in a new tag. And then we stop the propagation of the event so no other elements decided they will open the files in a new tab.

Now we need to actually listen to an event to detect the user’s files they are dropping. To do this, we need to add an event listener to the drop event on the form:

/
 Add an event listener for drop to the form
/
this.$refs.fileform.addEventListener(‘drop’, function(e){
 /

  Capture the files from the drop event and add them to our local files
  array.
 */
 for( let i = 0; i < e.dataTransfer.files.length; i++ ){
  this.files.push( e.dataTransfer.files[i] );
 }
}.bind(this));

There are a few features to point out with this event listener. First, we bind the local component with the .bind(this) method to the function we are handling the drop event with. This gives us the capability to reference the component directly and set local parameters.

Next, we iterate over all of the files being transferred. A user can select multiple files and drag/drop them onto the form. We need to grab all of these files and iterate over them adding the files to the local files array. To access the files, we reference the e.dataTransfer.files from the event to grab the files we need. This is what we iterate over and add each to the local files array. Now we can use these later on to send to our server! Each file is represented by the File object: File – Web APIs | MDN, which plays nicely when we send the file with FormData and axios.

This is the base for what we need for drag and drop file uploading! We will be adding a few bells and whistles kind of as a capstone for the other tutorials, but also as an example of what you can do with VueJS and Axios for file uploading.

Add Previews For Files Selected (if the files selected are image)

So now that we have the ability to drag and drop files to upload, let’s preview the files if they are images, or have a placeholder if they aren’t. This will be a nice UX feature just to see what they have selected. We will then allow the users to remove the files they don’t want to upload before continuing.

Display Files After Drag and Drop

The first step in the process will be to display the files selected by the user. We will need to iterate over the files array and display a simple preview if the file is an image.

In our template, right below the form, add the following markup:

<div v-for=“(file, key) in files” class=“file-listing”>
<img class=“preview” v-bind:ref=“‘preview’+parseInt( key )”/>
{{ file.name }}
</div>

What this does is iterates over all of the files in the files array making a simple preview template. In our template, the first element is an <img> tag that binds a reference to the preview and the key of the file in the filesarray. This is so we can set the src attribute through code if it is an image. We will be doing this in the next step.

The other piece of the template is just the name of the file provided by the user.

For a little bit of added style (so it’s not a complete disarray of elements) I added the following CSS to the <style> tag for the component:

div.file-listing{
width: 400px;
margin: auto;
padding: 10px;
border-bottom: 1px solid #ddd;
}

div.file-listing img{
height: 100px;
}

This will give us a nice row to display what each file is and allow us to add a button to remove the file if it’s no longer needed.

Implement File Preview if image

The most important feature of this step is to allow a file preview if the file is an image, otherwise show a default file. As I mentioned in the article: Preview File Uploads with Axios and VueJS, I’m sure there are other file types that can be previewed successfully, but for the purpose of this tutorial, we will just be implementing it with images.

The first line of code we add is in the event handler for drop. When fired, after we add the files to the local file array, we need to add this code:

this.getImagePreviews();

We will be implementing this method to display the file previews, but for now, we just need to call it in our event handler. This method will display the image preview or a placeholder image if the filetype is not an image.

Now, we need to add the method to the methods array in our component:

/*
 Gets the image preview for the file.
/
getImagePreviews(){
 /

  Iterate over all of the files and generate an image preview for each one.
 /
 for( let i = 0; i < this.files.length; i++ ){
  /

   Ensure the file is an image file
  /
  if ( /.(jpe?g|png|gif)$/i.test( this.files[i].name ) ) {
   /

    Create a new FileReader object
   */
   let reader = new FileReader();

   /*
    Add an event listener for when the file has been loaded
    to update the src on the file preview.
   */
   reader.addEventListener(“load”, function(){
    this.$refs[‘preview’+parseInt( i )][0].src = reader.result;
   }.bind(this), false);

   /*
    Read the data for the file in through the reader. When it has
    been loaded, we listen to the event propagated and set the image
    src to what was loaded from the reader.
   /
   reader.readAsDataURL( this.files[i] );
  }else{
   /

    We do the next tick so the reference is bound and we can access it.
   /
   this.$nextTick(function(){
    this.$refs[‘preview’+parseInt( i )][0].src = ‘/images/file.png’;
   });
  }
 }
}

There’s a lot to step through so let’s get started.

First, we iterate over all of the files stored in the local files array. This will allow us to generate the preview image for each file.

Next we check to see if the file type is that of an image:

if ( /.(jpe?g|png|gif)$/i.test( this.files[i].name ) ) {

We do this check because we want to display the actual image as a preview if the file is an image file. Otherwise we have a default icon that we display if the file is not an image.

If the file is an image, we initialize a file reader:

/
Create a new FileReader object
/
let reader = new FileReader();

We then bind a load event to the reader to allowing us to hook in and display an image once it’s been read from the file uploaded by the user:

/
Add an event listener for when the file has been loaded
to update the src on the file preview.
/
reader.addEventListener(“load”, function(){
this.$refs[‘preview’+parseInt( i )][0].src = reader.result;
}.bind(this), false);

We run a .bind(this) on the event listener we can access our global $refs and set the src of the img tag to the result of the reader which is the blob of the image. Remember, we are scoped inside of a check that the file is an image.

We reference the $ref with the value of i to bind the src to the right image tag. When we generate our template, we base the ref attribute on the key of the file in the files array.

Finally, for images, we read in the image with:

/
Read the data for the file in through the reader. When it has
been loaded, we listen to the event propagated and set the image
src to what was loaded from the reader.
/
reader.readAsDataURL( this.files[i] );

It reads in the image in the files array that has the index of i. When the file has been loaded (read) our event fill fire and set the src of the image tag to what was read in.

This works smoothly for images, but for other file types we want just a simple placeholder image. We do that with an else statement after checking for an image.

Since we are scoped outside of the file being an image, we know the file is a different file type. The catcher with this is we have to work inside the $nextTick method within VueJS. Since we are iterating over the files and VueJS is rendering the template, we have to make sure the references are bound so we can set the appropriate attribute on the src tag:

/
We do the next tick so the reference is bound and we can access it.
/
this.$nextTick(function(){
this.$refs[‘preview’+parseInt( i )][0].src = ‘/images/file.png’;
});

Now, the template is rendered and we can load the default file of our choice. We didn’t have to wait for the DOM to update on images, because the callback is on load which takes some time so our DOM is updated by the time the file is read in.

This is all we need to do to provide a simple image preview for the files uploaded.

Next, we will allow users to remove files they don’t want before submitting them to the server.

Allow users to remove the files they don’t want any more

This is one of the most important features when uploading multiple files at the same time is the ability to let users remove files that they didn’t want to upload. It can be a little bit tricky and we will be building it similar to what we built here in the first tutorial: Uploading Files With VueJS and Axios – Server Side Up.

Adjust template to allow for removal of files

Right off of the bat, we will need to adjust the template for when we iterate over the files to allow the user to remove a file. In the template add the following code at the end of where we iterate over the uploaded files:

<div class=“remove-container”>
<a class=“remove” v-on:click=“removeFile( key )”>Remove</a>
</div>

When clicked, we call the remove( key ) method that we will be implementing. The key is where we will slice the files array and remove the file.

Implement the removeFile( key ) method

The removeFile( key ) method is super simple to implement. Just add the following code to the methods object on your component:

/
Removes a select file the user has uploaded
*/
removeFile( key ){
this.files.splice( key, 1 );
}

This will splice the array at the key passed in from the user. When removed, VueJS will re-render the DOM and the file won’t display. When we submit the files array to the server the file will be removed so it won’t be submitted as well. That’s really all there is too it!

Even though we aren’t focusing much on CSS in this tutorial, I added the following for the remove functionality so it’s easy to click on:

div.remove-container{
text-align: center;
}

div.remove-container a{
color: red;
cursor: pointer;
}

Next up we will be actually making our request to send the files to the server.

Upload the Selected Files

In this step we will actually submit the request with the files to the server. This was covered in the first article I wrote here: Uploading Files With VueJS and Axios – Server Side Up. We will be using the FormData() class provided to do this request. This way we can bundle up the files we need and submit them with Axios to the server.

Add Button

The first step is to add the button to our component that the user can trigger to upload the files. To do this, add the following markup to the template after the display of the files that were selected:

<a class=“submit-button” v-on:click=“submitFiles()” v-show=“files.length > 0”>Submit</a>

The first attribute on the template we should note is the v-on:click=“submitFiles()” attribute. What this does is when the button is clicked, we call the submitFiles() method. We will be adding this in the next step.

Next, we should note the v-show=“files.length > 0” attribute. What this does is only shows the button if there are files uploaded. It’s a simple UX thing, but should be nice for the user.

I also added a few styles for the button:

a.submit-button{
display: block;
margin: auto;
text-align: center;
width: 200px;
padding: 10px;
text-transform: uppercase;
background-color: #CCC;
color: white;
font-weight: bold;
margin-top: 20px;
}

This just makes it a little easier to use if following along. Now it’s time to add our submitFiles() handler.

Add submitFiles() Handler

In the methods object on your component, add the following method:

/*
 Submits the files to the server
/
submitFiles(){
 /

  Initialize the form data
 */
 let formData = new FormData();

 /*
  Iteate over any file sent over appending the files
  to the form data.
 */
 for( var i = 0; i < this.files.length; i++ ){
  let file = this.files[i];

  formData.append(‘files[’ + i + ‘]’, file);
 }

 /*
  Make the request to the POST /file-drag-drop URL
 /
 axios.post( ‘/file-drag-drop’,
  formData,
  {
   headers: {
     ‘Content-Type’: ‘multipart/form-data’
   }
  }
 ).then(function(){
  console.log(‘SUCCESS!!’);
 })
 .catch(function(){
  console.log(‘FAILURE!!’);
 });
},

This is the actual magic of the file uploader right here! This is where it submits to the server. Let’s take a quick step through the process.

First, we initialize a FormData() object:

let formData = new FormData();

This allows us to build out our request to send to the server.

Next, up we add each of the files that are in the local files array to the FormData() so we send everything the user has selected:

/
 Iteate over any file sent over appending the files
 to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
 let file = this.files[i];

 formData.append(‘files[’ + i + ‘]’, file);
}

We append to the formData that we’ve created making sure each of the files is keyed appropriately.

Now, we make our actual Axios POST request:

/*
 Make the request to the POST /file-drag-drop URL
/
axios.post( ‘/file-drag-drop’,
 formData,
 {
  headers: {
    ‘Content-Type’: ‘multipart/form-data’
  }
 }
).then(function(){
 console.log(‘SUCCESS!!’);
})
.catch(function(){
 console.log(‘FAILURE!!’);
});

The first parameter in the request is the URL that we are submitting the files to. This is just an endpoint I set up on the test server to accept the file request. The second parameter is the formData that contains all of the files that were selected. This would also contain any other inputs that need to be sent along with the files such as text fields, booleans, etc.

The third parameter is the most important in this case. It allows us to configure our request and add extra headers. To send files to the server we need to add the ‘Content-Type’: ‘multipart/form-data’ header so the server knows to accept files if needed.

Finally, we hook into a successful request with the .then(function(){})appended to the request and an unsuccessful request with the .catch(function(){}). These can be used to display messages to your users accordingly. I just have them print to the console right now for testing.

That’s all we need for the actual request. Next, up we will be adding a progress bar to the drag and drop uploader. This will add a little better UX to the interface and can be styled to fit your needs.

Adding a progress bar

This is the last step before we actually submit the files to the server. First, add the markup to the template of your Vue Component to handle the progress bar:

<progress max=“100” :value.prop=“uploadPercentage”></progress>

I wrote a tutorial about the progress bar here: File Upload Progress Indicator with Axios and VueJS. I’ll quickly go over the features we need to worry about., but for more information check out that article.

The first attribute we should look at is the max attribute. This should be set to 100. What this means is that the progress will be between 0 and 100 which is great since we want to represent the file upload as a percentage.

The second attribute is the property value. Now this is unique since it’s not an input, the value property has to be defined as a property. This way we can have VueJS bind a reactive property to the element so it adjusts accordingly to the status of the component. The value which is bound to this property we will set in the next step.

I added the following styles to the styles tag to make the progress bar a little more usable for the time being:

progress{
 width: 400px;
 margin: auto;
 display: block;
 margin-top: 20px;
 margin-bottom: 20px;
}

Add data to the template

Now that we the template set, let’s add the uploadPercentage value to the data() method in the component like this:

/
 Variables used by the drag and drop component
*/
data(){
 return {
  dragAndDropCapable: false,
  files: [],
  uploadPercentage: 0
 }
},

We initialize this to 0 so as the percentage increases with the file upload amount, we can increase this as well.

Add listener to file upload request

In the last section we actually made the request with the files to the server. Now, we just need to add one more piece to the configuration for the request and that’s the listener to the upload progress event.

To add this listener in axis add the following to the request configuration (the third parameter):

onUploadProgress: function( progressEvent ) {
this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) );
}.bind(this)

What this does, is listens to the upload progress event and set the local uploadProgress variable to the percentage request’s completion. The event returns how much has been loaded (submitted) and how much needs to be submitted. We can divide the submitted * 100 by the total to get there percentage. This we can set to our local variable which then reactively sets our progress bar to visually represent the completion.

That’s about all we need to do for our drag and drop file uploader. Next up, we will be modifying our drag and drop component to do a direct upload with out a submit button.

For now, our component should look like this and be finalized with the basic amount of features:

<style>
 form{
  display: block;
  height: 400px;
  width: 400px;
  background: #ccc;
  margin: auto;
  margin-top: 40px;
  text-align: center;
  line-height: 400px;
  border-radius: 4px;
 }

 div.file-listing{
  width: 400px;
  margin: auto;
  padding: 10px;
  border-bottom: 1px solid #ddd;
 }

 div.file-listing img{
  height: 100px;
 }

 div.remove-container{
  text-align: center;
 }

 div.remove-container a{
  color: red;
  cursor: pointer;
 }

 a.submit-button{
  display: block;
  margin: auto;
  text-align: center;
  width: 200px;
  padding: 10px;
  text-transform: uppercase;
  background-color: #CCC;
  color: white;
  font-weight: bold;
  margin-top: 20px;
 }

 progress{
  width: 400px;
  margin: auto;
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
 }
</style>

<template>
 <div id=“file-drag-drop”>
  <form ref=“fileform”>
   <span class=“drop-files”>Drop the files here!</span>
  </form>

  <progress max=“100” :value.prop=“uploadPercentage”></progress>

  <div v-for=“(file, key) in files” class=“file-listing”>
   <img class=“preview” v-bind:ref=“‘preview’+parseInt( key )”/>
   {{ file.name }}
   <div class=“remove-container”>
    <a class=“remove” v-on:click=“removeFile( key )”>Remove</a>
   </div>
  </div>

  <a class=“submit-button” v-on:click=“submitFiles()” v-show=“files.length > 0”>Submit</a>
 </div>
</template>

<script>
 export default {
  /*
   Variables used by the drag and drop component
  */
  data(){
   return {
    dragAndDropCapable: false,
    files: [],
    uploadPercentage: 0
   }
  },

  mounted(){
   /*
    Determine if drag and drop functionality is capable in the browser
   */
   this.dragAndDropCapable = this.determineDragAndDropCapable();

   /*
    If drag and drop capable, then we continue to bind events to our elements.
   /
   if( this.dragAndDropCapable ){
    /

     Listen to all of the drag events and bind an event listener to each
     for the fileform.
    /
    [‘drag’, ‘dragstart’, ‘dragend’, ‘dragover’, ‘dragenter’, ‘dragleave’, ‘drop’].forEach( function( evt ) {
     /

      For each event add an event listener that prevents the default action
      (opening the file in the browser) and stop the propagation of the event (so
      no other elements open the file in the browser)
     */
     this.$refs.fileform.addEventListener(evt, function(e){
      e.preventDefault();
      e.stopPropagation();
     }.bind(this), false);
    }.bind(this));

    /*
     Add an event listener for drop to the form
    /
    this.$refs.fileform.addEventListener(‘drop’, function(e){
     /

      Capture the files from the drop event and add them to our local files
      array.
     */
     for( let i = 0; i < e.dataTransfer.files.length; i++ ){
      this.files.push( e.dataTransfer.files[i] );
      this.getImagePreviews();
     }
    }.bind(this));
   }
  },

  methods: {
   /*
    Determines if the drag and drop functionality is in the
    window
   /
   determineDragAndDropCapable(){
    /

     Create a test element to see if certain events
     are present that let us do drag and drop.
    */
    var div = document.createElement(‘div’);

    /*
     Check to see if the draggable event is in the element
     or the ondragstart and ondrop events are in the element. If
     they are, then we have what we need for dragging and dropping files.

     We also check to see if the window has FormData and FileReader objects
     present so we can do our AJAX uploading
    */
    return ( ( ‘draggable’ in div )
        || ( ‘ondragstart’ in div && ‘ondrop’ in div ) )
        && ‘FormData’ in window
        && ‘FileReader’ in window;
   },

   /*
    Gets the image preview for the file.
   /
   getImagePreviews(){
    /

     Iterate over all of the files and generate an image preview for each one.
    /
    for( let i = 0; i < this.files.length; i++ ){
     /

      Ensure the file is an image file
     /
     if ( /.(jpe?g|png|gif)$/i.test( this.files[i].name ) ) {
      /

       Create a new FileReader object
      */
      let reader = new FileReader();

      /*
       Add an event listener for when the file has been loaded
       to update the src on the file preview.
      */
      reader.addEventListener(“load”, function(){
       this.$refs[‘preview’+parseInt( i )][0].src = reader.result;
      }.bind(this), false);

      /*
       Read the data for the file in through the reader. When it has
       been loaded, we listen to the event propagated and set the image
       src to what was loaded from the reader.
      /
      reader.readAsDataURL( this.files[i] );
     }else{
      /

       We do the next tick so the reference is bound and we can access it.
      */
      this.$nextTick(function(){
       this.$refs[‘preview’+parseInt( i )][0].src = ‘/images/file.png’;
      });
     }
    }
   },

   /*
    Submits the files to the server
   /
   submitFiles(){
    /

     Initialize the form data
    */
    let formData = new FormData();

    /*
     Iteate over any file sent over appending the files
     to the form data.
    */
    for( var i = 0; i < this.files.length; i++ ){
     let file = this.files[i];

     formData.append(‘files[’ + i + ‘]’, file);
    }

    /*
     Make the request to the POST /file-drag-drop URL
    */
    axios.post( ‘/file-drag-drop’,
     formData,
     {
      headers: {
        ‘Content-Type’: ‘multipart/form-data’
      },
      onUploadProgress: function( progressEvent ) {
       this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) );
      }.bind(this)
     }
    ).then(function(){
     console.log(‘SUCCESS!!’);
    })
    .catch(function(){
     console.log(‘FAILURE!!’);
    });
   },

   /*
    Removes a select file the user has uploaded
   */
   removeFile( key ){
    this.files.splice( key, 1 );
   }
  }
 }
</script>

Direct Upload without Submit Button

This is a slightly different user experience that may fit the need for some users. Instead of the process being dragging and dropping of files, then verifying what was selected files and pressing submit, this simply uploads all of the files at once when they are dropped on the form.

For this section, I quickly built a new Vue component named FileDragDropInstant.vue and added the functionality we had from our drag and drop base. This will give us most of the functionality we need at the get go.

Our FileDragDropInstant.vue component should begin like this:

<style>
 form{
  display: block;
  height: 400px;
  width: 400px;
  background: #ccc;
  margin: auto;
  margin-top: 40px;
  text-align: center;
  line-height: 400px;
  border-radius: 4px;
 }
</style>

<template>
 <div id=“file-drag-drop”>
  <form ref=“fileform”>
   <span class=“drop-files”>Drop the files here!</span>
  </form>
 </div>
</template>

<script>
 export default {
  /*
   Variables used by the drag and drop component
  */
  data(){
   return {
    dragAndDropCapable: false,
    files: []
   }
  },

  mounted(){
   /*
    Determine if drag and drop functionality is capable in the browser
   */
   this.dragAndDropCapable = this.determineDragAndDropCapable();

   /*
    If drag and drop capable, then we continue to bind events to our elements.
   /
   if( this.dragAndDropCapable ){
    /

     Listen to all of the drag events and bind an event listener to each
     for the fileform.
    /
    [‘drag’, ‘dragstart’, ‘dragend’, ‘dragover’, ‘dragenter’, ‘dragleave’, ‘drop’].forEach( function( evt ) {
     /

      For each event add an event listener that prevents the default action
      (opening the file in the browser) and stop the propagation of the event (so
      no other elements open the file in the browser)
     */
     this.$refs.fileform.addEventListener(evt, function(e){
      e.preventDefault();
      e.stopPropagation();
     }.bind(this), false);
    }.bind(this));

    /*
     Add an event listener for drop to the form
    /
    this.$refs.fileform.addEventListener(‘drop’, function(e){
     /

      Capture the files from the drop event and add them to our local files
      array.
     */
     for( let i = 0; i < e.dataTransfer.files.length; i++ ){
      this.files.push( e.dataTransfer.files[i] );
     }
    }.bind(this));
   }
  },

  methods: {
   /*
    Determines if the drag and drop functionality is in the
    window
   /
   determineDragAndDropCapable(){
    /

     Create a test element to see if certain events
     are present that let us do drag and drop.
    */
    var div = document.createElement(‘div’);

    /*
     Check to see if the draggable event is in the element
     or the ondragstart and ondrop events are in the element. If
     they are, then we have what we need for dragging and dropping files.

     We also check to see if the window has FormData and FileReader objects
     present so we can do our AJAX uploading
    /
    return ( ( ‘draggable’ in div )
        || ( ‘ondragstart’ in div && ‘ondrop’ in div ) )
        && ‘FormData’ in window
        && ‘FileReader’ in window;
   }
  }
 }
</script>

It just simply provides a form where we can drag and drop a file over the form. We also check to see if the form is drag and drop capable.

When the file is dragged and dropped, we add it to the local files array. All of these steps I went through up above in the tutorial. Now we will add a few things to make the drag and drop instant.

Add submitFiles() method

This method is the same method we used in the other drag and drop upload component. What this does is submits the form with the files to the server. Add the following method to the methods object:

/
 Submits the files to the server
/
submitFiles(){
 /

  Initialize the form data
 */
 let formData = new FormData();

 /*
  Iteate over any file sent over appending the files
  to the form data.
 */
 for( var i = 0; i < this.files.length; i++ ){
  let file = this.files[i];

  formData.append(‘files[’ + i + ‘]’, file);
 }

 /*
  Make the request to the POST /file-drag-drop-instant URL
 */
 axios.post( ‘/file-drag-drop-instant’,
  formData,
  {
   headers: {
     ‘Content-Type’: ‘multipart/form-data’
   },
   onUploadProgress: function( progressEvent ) {
    this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) );
   }.bind(this)
  }
 ).then(function(){
  console.log(‘SUCCESS!!’);
 })
 .catch(function(){
  console.log(‘FAILURE!!’);
 });
}

What we also need to add, is add our uploadPercentage variable to our data() method and add the progress bar back to our template. Our component should now look like this:

<style>
 form{
  display: block;
  height: 400px;
  width: 400px;
  background: #ccc;
  margin: auto;
  margin-top: 40px;
  text-align: center;
  line-height: 400px;
  border-radius: 4px;
 }

 progress{
  width: 400px;
  margin: auto;
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
 }
</style>

<template>
 <div id=“file-drag-drop”>
  <form ref=“fileform”>
   <span class=“drop-files”>Drop the files here!</span>

   <progress max=“100” :value.prop=“uploadPercentage”></progress>
  </form>
 </div>
</template>

<script>
 export default {
  /*
   Variables used by the drag and drop component
  */
  data(){
   return {
    dragAndDropCapable: false,
    files: [],
    uploadPercentage: 0
   }
  },

  mounted(){
   /*
    Determine if drag and drop functionality is capable in the browser
   */
   this.dragAndDropCapable = this.determineDragAndDropCapable();

   /*
    If drag and drop capable, then we continue to bind events to our elements.
   /
   if( this.dragAndDropCapable ){
    /

     Listen to all of the drag events and bind an event listener to each
     for the fileform.
    /
    [‘drag’, ‘dragstart’, ‘dragend’, ‘dragover’, ‘dragenter’, ‘dragleave’, ‘drop’].forEach( function( evt ) {
     /

      For each event add an event listener that prevents the default action
      (opening the file in the browser) and stop the propagation of the event (so
      no other elements open the file in the browser)
     */
     this.$refs.fileform.addEventListener(evt, function(e){
      e.preventDefault();
      e.stopPropagation();
     }.bind(this), false);
    }.bind(this));

    /*
     Add an event listener for drop to the form
    /
    this.$refs.fileform.addEventListener(‘drop’, function(e){
     /

      Capture the files from the drop event and add them to our local files
      array.
     */
     for( let i = 0; i < e.dataTransfer.files.length; i++ ){
      this.files.push( e.dataTransfer.files[i] );
     }
    }.bind(this));
   }
  },

  methods: {
   /*
    Determines if the drag and drop functionality is in the
    window
   /
   determineDragAndDropCapable(){
    /

     Create a test element to see if certain events
     are present that let us do drag and drop.
    */
    var div = document.createElement(‘div’);

    /*
     Check to see if the draggable event is in the element
     or the ondragstart and ondrop events are in the element. If
     they are, then we have what we need for dragging and dropping files.

     We also check to see if the window has FormData and FileReader objects
     present so we can do our AJAX uploading
    */
    return ( ( ‘draggable’ in div )
        || ( ‘ondragstart’ in div && ‘ondrop’ in div ) )
        && ‘FormData’ in window
        && ‘FileReader’ in window;
   },

   /*
    Submits the files to the server
   /
   submitFiles(){
    /

     Initialize the form data
    */
    let formData = new FormData();

    /*
     Iteate over any file sent over appending the files
     to the form data.
    */
    for( var i = 0; i < this.files.length; i++ ){
     let file = this.files[i];

     formData.append(‘files[’ + i + ‘]’, file);
    }

    /*
     Make the request to the POST /file-drag-drop-instant URL
    /
    axios.post( ‘/file-drag-drop-instant’,
     formData,
     {
      headers: {
        ‘Content-Type’: ‘multipart/form-data’
      },
      onUploadProgress: function( progressEvent ) {
       this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) );
      }.bind(this)
     }
    ).then(function(){
     console.log(‘SUCCESS!!’);
    })
    .catch(function(){
     console.log(‘FAILURE!!’);
    });
   }
  }
 }
</script>

Right now we just allow the user to select files. We need to tie everything together and make it so when the user drops files on the form they get uploaded.

Tying Everything Together

The trick with this is to simply tie everything together by calling our submitFiles() method right after the user drops the files onto the form.

In our drop event handler in our mounted() lifecycle hook we simply need to add a call to the submitFiles() method.

Our handler should look like this:

/
 Add an event listener for drop to the form
/
this.$refs.fileform.addEventListener(‘drop’, function(e){
 /

  Capture the files from the drop event and add them to our local files
  array.
 */
 for( let i = 0; i < e.dataTransfer.files.length; i++ ){
  this.files.push( e.dataTransfer.files[i] );
 }

 /*
  Instantly upload files
 */
 this.submitFiles();
}.bind(this));

So now once a user drops files onto the form, we instantly submit the files to the server. That’s all there is to it! We added the progress bar to our element as well so this will show the progress as well.

Pointers

When doing drag and drop on an instant upload, there’s a few ways you could add some better UX (besides the obvious notifications of success or failure).

One could be to return the file URLs where they are on the server. This way you can remove the files right after upload or preview them outside of a FileReader().

Another thing is you could do an individual upload for each file. This could be interesting because you could do a progress bar for each file. To do this, you’d iterate over the files selected and create a request for each one and add a progress bar to the screen to show the status of each file.

Conclusion

So this is a quick step through of drag and drop file uploading using VueJS and Axios. Of course, let me know if there are any questions or any features that you’d like to see added.

Happy file uploading!

Thanks For Visiting and Keep Visiting…

☞ Beginner’s Guide to Vue.js

☞ Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

☞ Build a Progressive Web App In VueJs

☞ Build a CMS with Laravel and Vue

☞ Hands-on Vue.js for Beginners

☞ Top 3 Mistakes That Vue.js Developers Make and Should be Avoided

☞ Vue.js Tutorial: Zero to Sixty

How to Build a Portfolio Site with Vue, Bulma, and Airtable


Originally published on https://serversideup.net

#vue-js #javascript #web-development

What is GEEK

Buddha Community

How to Drag and Drop File Uploads with Vue.js and Axios

Drag and Drop File Upload Using Dropzone js in Laravel 8

Hello Friends,

In this tutorial i will show you Drag and Drop File Upload Using Dropzone js in Laravel 8 using dropzone.js. DropzoneJS is an open source library that provides drag and drop file uploads with image previews.

Read More : Drag and Drop File Upload Using Dropzone js in Laravel 8

https://websolutionstuff.com/post/drag-and-drop-file-upload-using-dropzone-js-in-laravel-8


Read Also : Google Recaptcha Example In Laravel

https://websolutionstuff.com/post/google-recaptcha-example-in-laravel


Read Also : Send Mail Example In Laravel 8

https://websolutionstuff.com/post/send-mail-example-in-laravel-8

#drag and drop file upload using dropzone js in laravel 8 #laravel 8 #file upload #dropzone js #drag and drop #multiple files upload

Nina Diana

Nina Diana

1578050760

10 Best Vue Drag and Drop Component For Your App

Vue Drag and drop is a feature of many interactive web apps. It provides an intuitive way for users to manipulate their data. Adding drag and drop feature is easy to add to Vue.js apps.

Here are 10 vue drop components that contribute to the flexibility of your vue application.

1. Vue.Draggable

Vue component (Vue.js 2.0) or directive (Vue.js 1.0) allowing drag-and-drop and synchronization with view model array.

Based on and offering all features of Sortable.js

Vue.Draggable

Demo: https://sortablejs.github.io/Vue.Draggable/#/simple

Download: https://github.com/SortableJS/Vue.Draggable/archive/master.zip

2. realtime-kanban-vue

Real-time kanban board built with Vue.js and powered by Hamoni Sync.

realtime-kanban-vue

Demo: https://dev.to/pmbanugo/real-time-kanban-board-with-vuejs-and-hamoni-sync-52kg

Download: https://github.com/pmbanugo/realtime-kanban-vue/archive/master.zip

3. vue-nestable

Drag & drop hierarchical list made as a vue component.

Goals

  • A simple vue component to create a draggable list to customizable items
  • Reorder items by dragging them above an other item
  • Intuitively nest items by dragging right
  • Fully customizable, ships with no css
  • Everything is configurable: item identifier, max nesting level, threshold for nesting

vue-nestable

Demo: https://rhwilr.github.io/vue-nestable/

Download: https://github.com/rhwilr/vue-nestable/archive/master.zip

4. VueDraggable

VueJS directive for drag and drop.

Native HTML5 drag and drop implementation made for VueJS.

VueDraggable

Demo: https://vivify-ideas.github.io/vue-draggable/

Download: https://github.com/Vivify-Ideas/vue-draggable/archive/master.zip

5. vue-grid-layout

vue-grid-layout is a grid layout system, like Gridster, for Vue.js. Heavily inspired in React-Grid-Layout

vue-grid-layout

Demo: https://jbaysolutions.github.io/vue-grid-layout/examples/01-basic.html

Download: https://github.com/jbaysolutions/vue-grid-layout/archive/master.zip

6. vue-drag-tree

It’s a tree components(Vue2.x) that allow you to drag and drop the node to exchange their data .

Feature

  • Double click on an node to turn it into a folder
  • Drag and Drop the tree node, even between two different levels
  • Controls whether a particular node can be dragged and whether the node can be plugged into other nodes
  • Append/Remove Node in any level (#TODO)

vue-drag-tree

Demo: https://vigilant-curran-d6fec6.netlify.com/#/

Download: https://github.com/shuiRong/vue-drag-tree/archive/master.zip

7. VueDragDrop

A Simple Drag & Drop example created in Vue.js.

VueDragDrop

Demo: https://seregpie.github.io/VueDragDrop/

Download: https://github.com/SeregPie/VueDragDrop/archive/master.zip

8. Vue-drag-resize

Vue Component for resize and drag elements.

Vue-drag-resize

Demo: http://kirillmurashov.com/vue-drag-resize/

Download: https://github.com/kirillmurashov/vue-drag-resize/archive/master.zip

9. vue-smooth-dnd

A fast and lightweight drag&drop, sortable library for Vue.js with many configuration options covering many d&d scenarios.

This library consists wrapper Vue.js components over smooth-dnd library.

Show, don’t tell !

vue-smooth-dnd

Demo: https://kutlugsahin.github.io/vue-smooth-dnd/#/cards

Download: https://github.com/kutlugsahin/vue-smooth-dnd/archive/master.zip

10. vue-dragula

Drag and drop so simple it hurts

vue-dragula

Demo: http://astray-git.github.io/vue-dragula/

Download: https://github.com/Astray-git/vue-dragula/archive/master.zip

#vue #vue-drag #vue-drop #drag-and-drop #vue-drag-and-drop

Aria Barnes

Aria Barnes

1625232484

Why is Vue JS the most Preferred Choice for Responsive Web Application Development?

For more than two decades, JavaScript has facilitated businesses to develop responsive web applications for their customers. Used both client and server-side, JavaScript enables you to bring dynamics to pages through expanded functionality and real-time modifications.

Did you know!

According to a web development survey 2020, JavaScript is the most used language for the 8th year, with 67.7% of people choosing it. With this came up several javascript frameworks for frontend, backend development, or even testing.

And one such framework is Vue.Js. It is used to build simple projects and can also be advanced to create sophisticated apps using state-of-the-art tools. Beyond that, some other solid reasons give Vuejs a thumbs up for responsive web application development.

Want to know them? Then follow this blog until the end. Through this article, I will describe all the reasons and benefits of Vue js development. So, stay tuned.

Vue.Js - A Brief Introduction

Released in the year 2014 for public use, Vue.Js is an open-source JavaScript framework used to create UIs and single-page applications. It has over 77.4 million likes on Github for creating intuitive web interfaces.

The recent version is Vue.js 2.6, and is the second most preferred framework according to Stack Overflow Developer Survey 2019.

Every Vue.js development company is widely using the framework across the world for responsive web application development. It is centered around the view layer, provides a lot of functionality for the view layer, and builds single-page web applications.

Some most astonishing stats about Vue.Js:

• Vue was ranked #2 in the Front End JavaScript Framework rankings in the State of JS 2019 survey by developers.

• Approximately 427k to 693k sites are built with Vue js, according to Wappalyzer and BuiltWith statistics of June 2020.

• According to the State of JS 2019 survey, 40.5% of JavaScript developers are currently using Vue, while 34.5% have shown keen interest in using it in the future.

• In Stack Overflow's Developer Survey 2020, Vue was ranked the 3rd most popular front-end JavaScript framework.

Why is Vue.Js so popular?

• High-speed run-time performance
• Vue.Js uses a virtual DOM.
• The main focus is on the core library, while the collaborating libraries handle other features such as global state management and routing.
• Vue.JS provides responsive visual components.

Top 7 Reasons to Choose Vue JS for Web Application Development

Vue js development has certain benefits, which will encourage you to use it in your projects. For example, Vue.js is similar to Angular and React in many aspects, and it continues to enjoy increasing popularity compared to other frameworks.

The framework is only 20 kilobytes in size, making it easy for you to download files instantly. Vue.js easily beats other frameworks when it comes to loading times and usage.

Take a look at the compelling advantages of using Vue.Js for web app development.

#1 Simple Integration

Vue.Js is popular because it allows you to integrate Vue.js into other frameworks such as React, enabling you to customize the project as per your needs and requirements.

It helps you build apps with Vue.js from scratch and introduce Vue.js elements into their existing apps. Due to its ease of integration, Vue.js is becoming a popular choice for web development as it can be used with various existing web applications.

You can feel free to include Vue.js CDN and start using it. Most third-party Vue components and libraries are additionally accessible and supported with the Vue.js CDN.

You don't need to set up node and npm to start using Vue.js. This implies that it helps develop new web applications, just like modifying previous applications.

The diversity of components allows you to create different types of web applications and replace existing frameworks. In addition, you can also choose to hire Vue js developers to use the technology to experiment with many other JavaScript applications.

#2 Easy to Understand

One of the main reasons for the growing popularity of Vue.Js is that the framework is straightforward to understand for individuals. This means that you can easily add Vue.Js to your web projects.

Also, Vue.Js has a well-defined architecture for storing your data with life-cycle and custom methods. Vue.Js also provides additional features such as watchers, directives, and computed properties, making it extremely easy to build modern apps and web applications with ease.

Another significant advantage of using the Vue.Js framework is that it makes it easy to build small and large-scale web applications in the shortest amount of time.

#3 Well-defined Ecosystem

The VueJS ecosystem is vibrant and well-defined, allowing Vue.Js development company to switch users to VueJS over other frameworks for web app development.

Without spending hours, you can easily find solutions to your problems. Furthermore, VueJs lets you choose only the building blocks you need.

Although the main focus of Vue is the view layer, with the help of Vue Router, Vue Test Utils, Vuex, and Vue CLI, you can find solutions and recommendations for frequently occurring problems.

The problems fall into these categories, and hence it becomes easy for programmers to get started with coding right away and not waste time figuring out how to use these tools.

The Vue ecosystem is easy to customize and scales between a library and a framework. Compared to other frameworks, its development speed is excellent, and it can also integrate different projects. This is the reason why most website development companies also prefer the Vue.Js ecosystem over others.

#4 Flexibility

Another benefit of going with Vue.Js for web app development needs is flexibility. Vue.Js provides an excellent level of flexibility. And makes it easier for web app development companies to write their templates in HTML, JavaScript, or pure JavaScript using virtual nodes.

Another significant benefit of using Vue.Js is that it makes it easier for developers to work with tools like templating engines, CSS preprocessors, and type checking tools like TypeScript.

#5 Two-Way Communication

Vue.Js is an excellent option for you because it encourages two-way communication. This has become possible with the MVVM architecture to handle HTML blocks. In this way, Vue.Js is very similar to Angular.Js, making it easier to handle HTML blocks as well.

With Vue.Js, two-way data binding is straightforward. This means that any changes made by the developer to the UI are passed to the data, and the changes made to the data are reflected in the UI.

This is also one reason why Vue.Js is also known as reactive because it can react to changes made to the data. This sets it apart from other libraries such as React.Js, which are designed to support only one-way communication.

#6 Detailed Documentation

One essential thing is well-defined documentation that helps you understand the required mechanism and build your application with ease. It shows all the options offered by the framework and related best practice examples.

Vue has excellent docs, and its API references are one of the best in the industry. They are well written, clear, and accessible in dealing with everything you need to know to build a Vue application.

Besides, the documentation at Vue.js is constantly improved and updated. It also includes a simple introductory guide and an excellent overview of the API. Perhaps, this is one of the most detailed documentation available for this type of language.

#7 Large Community Support

Support for the platform is impressive. In 2018, support continued to impress as every question was answered diligently. Over 6,200 problems were solved with an average resolution time of just six hours.

To support the community, there are frequent release cycles of updated information. Furthermore, the community continues to grow and develop with backend support from developers.



Wrapping Up

VueJS is an incredible choice for responsive web app development. Since it is lightweight and user-friendly, it builds a fast and integrated web application. The capabilities and potential of VueJS for web app development are extensive.

While Vuejs is simple to get started with, using it to build scalable web apps requires professionalism. Hence, you can approach a top Vue js development company in India to develop high-performing web apps.

Equipped with all the above features, it doesn't matter whether you want to build a small concept app or a full-fledged web app; Vue.Js is the most performant you can rely on.

Original source

 

#vue js development company #vue js development company in india #vue js development company india #vue js development services #vue js development #vue js development companies

I am Developer

1597559012

Multiple File Upload in Laravel 7, 6

in this post, i will show you easy steps for multiple file upload in laravel 7, 6.

As well as how to validate file type, size before uploading to database in laravel.

Laravel 7/6 Multiple File Upload

You can easily upload multiple file with validation in laravel application using the following steps:

  1. Download Laravel Fresh New Setup
  2. Setup Database Credentials
  3. Generate Migration & Model For File
  4. Make Route For File uploading
  5. Create File Controller & Methods
  6. Create Multiple File Blade View
  7. Run Development Server

https://www.tutsmake.com/laravel-6-multiple-file-upload-with-validation-example/

#laravel multiple file upload validation #multiple file upload in laravel 7 #multiple file upload in laravel 6 #upload multiple files laravel 7 #upload multiple files in laravel 6 #upload multiple files php laravel

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript