Created
October 11, 2017 14:16
-
-
Save mflisikowski/8c1a3399bcfb96df717facce7c4d8f69 to your computer and use it in GitHub Desktop.
Vue.js upload component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <template lang="pug"> | |
| div(class="gwp-contest__field", :class="{'has-error': errors.has(schema.model) }") | |
| div(class="dropzone") | |
| input(class="is-hidden", type="text", value="", ref="uploadedOutput") | |
| div(class="dropzone__upload") | |
| div(class="dropzone__text") | |
| div(class="dropzone__title") | |
| div(v-if="(howMany !== maxItems)") | |
| | {{ dropMsg }} | |
| div(v-if="(howMany < minItems)") | |
| | {{ dropMinMsg }}: [{{ minItems }}] | |
| div(v-if="(howMany >= minItems && howMany < maxItems)") | |
| | {{ dropMaxMsg }}: [{{ maxItems }}] | |
| div(v-if="(howMany >= maxItems)") | |
| | {{ dropSuccessMsg }} | |
| input( | |
| ref="uploadInput", | |
| v-validate="hasValidators()", | |
| :name="schema.inputName", | |
| class="dropzone-area__input", | |
| @change="onFileChange", | |
| type="file", | |
| :accept="schema.accept", | |
| :multiple="schema.multiple" | |
| ) | |
| div(class="dropzone__items") | |
| div(class="dropzone__item", v-for="(item, index) in uploaded") | |
| button(class="dropzone__remove", @click.prevent="removeImage(index)") | |
| svg(class="dropzone__image", viewBox="0 0 1 1", width="100%", height="100%") | |
| image(:xlink:href="item", width="100%", height="100%", preserveAspectRatio="xMidYMid slice") | |
| div(v-if="errors.has(schema.model)", :class="{'has-error': errors.has(schema.model)}") | |
| | {{ errors.first(schema.model) }} | |
| </template> | |
| <script> | |
| import ImageCompressor from '@xkeshi/image-compressor' | |
| import fieldsMixin from '../../mixins/fields' | |
| import { API_POINT } from '../../constants' | |
| export default { | |
| mixins: [ fieldsMixin ], | |
| data () { | |
| return { | |
| formData: null, | |
| formTimeout: (index) => index * 1000, | |
| formAccept: 'image/jpg;image/jpeg;image/png', | |
| formHeaders: { | |
| headers: {'Content-Type': 'multipart/form-data'} | |
| }, | |
| dropSuccessMsg: this.schema.dropSuccessMsg || 'Dziękujemy za dodanie zdjęć', | |
| dropMinMsg: this.schema.dropMinMsg || 'Minimalna liczba zdjęć to', | |
| dropMaxMsg: this.schema.dropMaxMsg || 'Maksymalna liczba zdjęć to', | |
| dropMsg: this.schema.dropMsg || 'Upuść obraz lub wybierz', | |
| dropSupport: null, | |
| files: [], | |
| uploaded: [], | |
| howMany: null, | |
| compressed: this.schema.compressed || false, | |
| minItems: this.schema.minItems || 1, | |
| maxItems: this.schema.maxItems || 3 | |
| } | |
| }, | |
| computed: { | |
| correctDropMsg () { | |
| return (this.dropSupport) ? 'true' : 'false' // this.dropMsg | |
| } | |
| }, | |
| watch: { | |
| uploaded () { | |
| this.howMany = this.uploaded.length | |
| this.visibilityDropArea() | |
| } | |
| }, | |
| mounted () { | |
| this.dropSupport = this.supportsDragAndDrop() | |
| }, | |
| methods: { | |
| onFileChange (e) { | |
| this.files = e.target.files || e.dataTransfer.files | |
| if (!this.files.length) return | |
| return [].filter.call(this.files, (x) => x.type.match(/jpg|jpeg|png|gif$/)).map((file, index) => { | |
| this.makeFormDataSubmit(file, index) | |
| }) | |
| }, | |
| onFileDrop (e) { | |
| e.preventDefault() | |
| console.log('onFileDrop') | |
| }, | |
| onFileDragover (e) { | |
| e.preventDefault() | |
| console.log('onFileDragover') | |
| }, | |
| supportsDragAndDrop () { | |
| let div = document.createElement('div') | |
| return ('ondragover' in div && 'ondrop' in div) | |
| }, | |
| visibilityDropArea () { | |
| if (this.howMany >= this.maxItems) { | |
| this.$refs.uploadInput.style.display = 'none' | |
| } else { | |
| this.$refs.uploadInput.style.display = 'block' | |
| } | |
| }, | |
| removeImage (index) { | |
| this.uploaded.splice(index, 1) | |
| }, | |
| storeOfSavedImages (array) { | |
| this.$refs.uploadedOutput.value = JSON.stringify(array) | |
| }, | |
| saveUrlsOfImages (url) { | |
| this.uploaded.push(url) | |
| this.storeOfSavedImages(this.uploaded) | |
| }, | |
| makeRandomName (fileName) { | |
| return `${Math.random().toString(36).substring(2, 48)}.${fileName.split('.')[1]}` | |
| }, | |
| makeFormData (file, name) { | |
| this.formData = new FormData() | |
| this.formData.append('accept', this.formAccept) | |
| this.formData.append('file', file, name) | |
| }, | |
| makeImageWithoutCompress (file) { | |
| this.makeFormData(file, this.makeRandomName(file.name)) | |
| this.makePostRequest(API_POINT.upload, this.formData, this.formHeaders) | |
| }, | |
| makeImageWithCompress (file) { | |
| /* eslint-disable no-new */ | |
| new ImageCompressor(file, { | |
| quality: 0.6, | |
| success: (result) => { | |
| this.makeFormData(result, this.makeRandomName(file.name)) | |
| this.makePostRequest(API_POINT.upload, this.formData, this.formHeaders) | |
| } | |
| }) | |
| }, | |
| makePostRequest (point, data, config) { | |
| this.$http.post(point, data, config) | |
| .then((response) => { | |
| this.saveUrlsOfImages(response.data.url) | |
| }).catch((err) => { | |
| if (err.status === 406) { | |
| console.log(`Problem accessing. Reason: Not Acceptable`) | |
| } | |
| }) | |
| }, | |
| makeFormDataSubmit (file, index) { | |
| setTimeout(() => { | |
| if (this.errors.items.length === 0) { | |
| (this.compressed) ? this.makeImageWithCompress(file) : this.makeImageWithoutCompress(file) | |
| } | |
| }, this.formTimeout(index)) | |
| } | |
| } | |
| } | |
| </script> | |
| <style lang="scss"> | |
| .dropzone | |
| { | |
| border-style: dashed; | |
| border-width: 2px; | |
| border-color: silver; | |
| height: auto; | |
| min-height: 200px; | |
| font-size: 14px; | |
| margin: 10px 0; | |
| } | |
| .dropzone__remove | |
| { | |
| position: absolute; | |
| border: none; | |
| background: none; | |
| display: block; | |
| width: 60px; | |
| height: 60px; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| z-index: 1; | |
| cursor: pointer; | |
| visibility: hidden; | |
| transition: all 230ms cubic-bezier(0.42, 0, 0.58, 1); | |
| will-change: visibility; | |
| background-color: white; | |
| border-radius: 50%; | |
| &:focus | |
| { | |
| outline: none; | |
| } | |
| &::before | |
| { | |
| transform: translate(-50%, -50%) rotate(-45deg); | |
| } | |
| &::after | |
| { | |
| transform: translate(-50%, -50%) rotate(45deg); | |
| } | |
| &::before, | |
| &::after { | |
| top: 50%; | |
| left: 50%; | |
| position: absolute; | |
| content: ''; | |
| width: 22px; | |
| height: 4px; | |
| border-radius: 4px; | |
| background-color: #000; | |
| display: block; | |
| } | |
| } | |
| .dropzone__upload | |
| { | |
| position: relative; | |
| min-height: 200px; | |
| .dropzone__text | |
| { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| } | |
| .dropzone-area__input | |
| { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; | |
| right: 0; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0; | |
| } | |
| .dropzone__text | |
| { | |
| text-align: center; | |
| line-height: 1.6; | |
| width: 100%; | |
| max-width: 960px; | |
| span | |
| { | |
| display: block; | |
| } | |
| } | |
| .dropzone__title > .has-error { | |
| color: red; | |
| } | |
| .dropzone__items | |
| { | |
| margin: 0; | |
| padding: 0; | |
| list-style: none; | |
| box-sizing: border-box; | |
| &::after | |
| { | |
| content: ''; | |
| display: table; | |
| clear: both; | |
| } | |
| .dropzone__text | |
| { | |
| margin: 10px 0; | |
| padding: 10px; | |
| } | |
| } | |
| .dropzone__item | |
| { | |
| position: relative; | |
| float: left; | |
| display: inline-block; | |
| margin: 5px; | |
| width: calc((100% / 3) - 10px); | |
| box-sizing: border-box; | |
| &:nth-child(4) | |
| { | |
| clear: both | |
| } | |
| &:hover | |
| { | |
| .dropzone__remove | |
| { | |
| visibility: visible; | |
| } | |
| .dropzone__image | |
| { | |
| opacity: .5; | |
| } | |
| } | |
| } | |
| .dropzone__image | |
| { | |
| max-width: 100%; | |
| margin: 0 auto; | |
| z-index: 0; | |
| transition: all 250ms cubic-bezier(0.42, 0, 0.58, 1); | |
| will-change: opacity; | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment