No More Posting New Topics!

If you have a question or an issue, please start a thread in our Github Discussions Forum.
This forum is closed for new threads/ topics.

Navigation

    Quasar Framework

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search

    I need suggestions

    Help
    4
    40
    2550
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Z
      zeppelinexpress last edited by zeppelinexpress

      I’m starting a profile admin menu, and need suggestions about which is the better way to set and update profile picture, I have no idea where I start, if I use file picker (with avatar, so user can see preview pic with right format (round)) or uploader. the better way to solve it.
      I tried today with uploader, but had some issues to update, when user open his profile and I try to pick his pic uploaded before, I tried to do something like this:

      const file = {
        size: 0,
        type: '.jpg',
        __img: {
         src: this.form.userImagePath
        }
      }
      this.$refs.imgup.addFiles([file])
      

      but I think this is not the better way to do

      beets 1 Reply Last reply Reply Quote 0
      • beets
        beets @zeppelinexpress last edited by beets

        Edit: See this codesandbox for an example: https://codesandbox.io/s/loving-glade-7eigi

        @zeppelinexpress What I do, is have a component called user-avatar that accepts an image prop that’s an object. Inside that component, I check for either blob or url key on the image object. In either case, it renders a simple image with the src value being either the url or blob. Here’s the code for that (render function used, name it user-avatar.js) (Edit: see next post for non-render function of the same, in which case, call it user-avatar.vue)

        export default {
          functional: true,
          props: {
            image: {
              type: Object,
            },
            size: {
              type: String,
              default: '100px'
            },
          },
          render: function (createElement, context) {
            let imageEl
            if(context.props.image) {
              if(context.props.image.blob) {
                imageEl = createElement('img', {
                  attrs: {
                    draggable: false,
                    src: context.props.image.blob,
                    class: 'fit'
                  },
                  style: {
                    objectFit: 'cover',
                  }
                })
              } else {
                imageEl = createElement('img', {
                  attrs: {
                    draggable: false,
                    src: context.props.image.url,
                  },
                })
              }
            } else {
              imageEl = createElement('img', {
                attrs: {
                  draggable: false,
                  src: 'http://example.com/default-image.png'
                },
              })
            }
            return createElement('div', {
              on: context.data.on,
              style: {
                width: context.props.size,
                height: context.props.size,
                borderRadius: '50%',
              },
              class: {
                ...(context.data.staticClass && {
                  [context.data.staticClass]: true,
                }),
                'overflow-hidden': true,
                'non-selectable': true,
              },
            }, [
              imageEl
            ])
          }
        }
        

        Then, I have another component called image-picker that uses neither quasar’s file picker or uploader component. (regular vue component, name something like image-picker.vue)

        <template>
        <div
          @dragenter.prevent="onDragEnter"
          @dragover.prevent="onDragOver"
          @dragleave.prevent="onDragLeave"
          @drop.prevent="onDrop"
          @click="$refs.input.click()"
          class="relative-position cursor-pointer file-picker q-pa-md"
          :class="{ dragging }"
          >
          <div>Click to select or drag and drop an image</div>
          <input
            ref="input"
            type="file"
            accept="image/*"
            @change="onInput"
            class="hidden"
            />
        </div>
        </template>
        
        <script>
        export default {
          data() {
            return {
              dragging: false,
            }
          },
          methods: {
            onDragEnter() {
              this.dragging = true
            },
            onDragOver() {
              this.dragging = true
            },
            onDragLeave() {
              this.dragging = false
            },
            onDrop(e) {
              this.dragging = false
              this.$refs.input.files = e.dataTransfer.files
              this.update(e.dataTransfer.files[0])
            },
            onInput(e) {
              this.update(e.target.files[0])
            },
            update(file) {
              this.$emit('input', {
                file,
              })
            },
            onRejected(rejectedEntries) {
              this.$q.notify({
                type: 'negative',
                message: `${rejectedEntries.length} file(s) did not pass validation constraints`
              })
            }
          }
        }
        </script>
        

        Then the parent component that holds the two above. This will show the current avatar from your API, or the new file if selected.

        <template>
        <div>
        
          <user-avatar
            :image="avatar"
            />
        
          <image-picker
            @input="onInput"
            />
        
        </div>
        </template>
        
        <script>
        import ImagePicker from './image-picker'
        import UserAvatar from './user-avatar'
        
        export default {
          data() {
            return {
              newAvatar: null
            }
          },
          computed: {
            currentAvatar() {
              return this.$store.state.path.to.avatar // should be object like { url: 'http://example.com/beets.png' }
            },
            avatar() {
              return this.newAvatar || this.currentAvatar // this will be the selected file if exists, else our current avatar
            }
          },
          methods: {
            onInput({ file }) {
              if(file instanceof File) {
                // Read the file, and convert to blob
                const reader = new FileReader()
                reader.onload = (e) => {
                  this.newAvatar = {
                    file,
                    blob: e.target.result,
                  }
                }
                reader.readAsDataURL(file)
              }
            },
          },
          components: {
            ImagePicker,
            UserAvatar,
          }
        }
        </script>
        

        Finally, when you want to submit the form for the user to save the new avatar, do something like this:

        const formData = new FormData()
        formData.append('somefield', 'somevalue') // append other fields
        
        if(this.newAvatar) {
          if(this.newAvatar.file instanceof File) {
            formData.append('avatar', this.newAvatar.file)
          }
        }
        
        // Submit FormData through axios
        
        

        What this lets me do, is use the same user-avatar component throughout the app, and also let me use it to display either the current avatar or a blob of a file in the account update form.

        Note: all this code was stripped from various files, and I haven’t tested it, but it should hopefully help you.

        I 1 Reply Last reply Reply Quote 1
        • beets
          beets last edited by beets

          Also, in case you’re not used to render functions, that very first component would be written something like this:

          <template>
          <div
            class="overflow-hidden non-selectable"
            :style="{ width: size, height: size, 'border-radius': '50%' }"
            >
            <img 
              v-if="image && image.blob"
              :src="image.blob"
              draggable="false"
              class="fit"
              style="object-fit: cover"
              />
            <img 
              v-else-if="image && image.url"
              :src="image.url"
              draggable="false"
              />
            <img
              v-else
              src="http://example.com/default-image.png"
              />
          </div>
          </template>
          <script>
          export default {
            props: {
              image: {
                type: Object,
              },
              size: {
                type: String,
                default: '100px'
              },
            },
          }
          </script>
          
          1 Reply Last reply Reply Quote 1
          • Z
            zeppelinexpress last edited by

            thanks @beets, gonna try right now

            beets 1 Reply Last reply Reply Quote 0
            • beets
              beets @zeppelinexpress last edited by

              @zeppelinexpress Let us know if it works for you. You may have to adapt it a bit since I copy / pasted and reorganized a bit from my app, but the general idea works great. You can also kind of merge the image-picker and parent component so it pops up a file select when you click the avatar, which is a bit nicer ux wise.

              1 Reply Last reply Reply Quote 1
              • Z
                zeppelinexpress last edited by

                I got your general idea, I’m trying to do using your thought, working so far, thank you one more time @beets

                1 Reply Last reply Reply Quote 0
                • I
                  Incremental @beets last edited by Incremental

                  @beets thanks for sharing this code.
                  I’m trying to use it in a profile.vue

                  <template>
                  	<div>
                  		<user-avatar :image="avatar" />
                  		<image-picker @input="onInput" />
                  	</div>
                  </template>
                  
                  <script>
                  import ImagePicker from "../components/user/image-picker";
                  import UserAvatar from "../components/user/user-avatar";
                  
                  export default {
                  	name: "Profile",
                  	components: { ImagePicker, UserAvatar },
                  
                  	data() {
                  		return {
                  			// my vars...
                  			Avatar: this.$store.state.server.user.avatar_fullpath,
                  	},
                  	computed: {
                  		currentAvatar() {
                  			//return this.$store.state.path.to.avatar; // should be object like { url: 'http://example.com/beets.png' }
                  			return this.$store.state.server.user.avatar_fullpath;
                  		},
                  		avatar() {
                  			return this.newAvatar || this.currentAvatar; // this will be the selected file if exists, else our current avatar
                  		}
                  	},
                  </script>
                  
                  1. when I click, a popup asks me for a file. I’ve put logs, and the file is selected, but after, nothing happens, the pic is not rendered.
                    It seems that :
                  if (file instanceof File) {} is not executed
                  

                  even if file contains a file

                  1. image-picker doesn’t call onRejected(rejectedEntries) {}
                  2. how to have my default avatar instead of the default picture ?
                  imageEl = createElement("img", {
                  	attrs: {
                  		draggable: false,
                  		src: "http://example.com/default-image.png"
                  	}
                  });
                  
                  1 Reply Last reply Reply Quote 0
                  • beets
                    beets last edited by

                    @incremental said in I need suggestions:

                    It seems that :

                    if (file instanceof File) {} is not executed

                    Hm, in your profile.vue I don’t see the onInput method. That would be where the if condition should live. Do you have it somewhere else in your code?

                    image-picker doesn’t call onRejected(rejectedEntries) {}

                    Looking at the code I posted, I somehow never call that method. Maybe it’s also a bug in my code or I forgot to copy/paste some code. Since we aren’t using Quasar’s native file pickers, we would have to roll our own validation checks and call the onRejected ourselves. Something like:

                        update(file) {
                          if(file.size > 1024 * 1024 * 5) {
                            this.onRejected('Max file size is 5Mb')
                            return
                          }
                          if(file.type != 'image/png' && file.type != 'image/jpeg' && file.type != 'image/gif') {
                            this.onRejected('Not an image')
                            return
                          }
                          this.$emit('input', {
                            file,
                          })
                        },
                        onRejected(message) {
                          this.$q.notify({
                            type: 'negative',
                            message: `File did not pass validation constraints, ${message}`
                          })
                        }
                    
                    

                    how to have my default avatar instead of the default picture ?

                    Instead of "http://example.com/default-image.png" you could use "default-image.png" and place default-image.png in the public folder. That should work. In my case, the default image was stored on the server instead of on my local machine for testing, but either way works.

                    I 1 Reply Last reply Reply Quote 0
                    • I
                      Incremental @beets last edited by

                      @beets
                      thanks for onRejected(rejectedEntries) {}

                      In my profile.vue, I have :

                      	methods: {
                      		onInput(file) {
                      			console.log("Test.vue - onInput() : ", file);
                      			if (this.file instanceof File) {
                      				// Read the file, and convert to blob
                      				const reader = new FileReader();
                      				reader.onload = e => {
                      					this.newAvatar = {
                      						file,
                      						blob: e.target.result
                      					};
                      				};
                      				reader.readAsDataURL(file);
                      			}
                      		},
                      

                      but the if (this.file instanceof File) {} is always false.
                      I tried to suppress it but I get :
                      [Vue warn]: Error in v-on handler: “TypeError: FileReader.readAsDataURL: Argument 1 does not implement interface Blob.”

                      beets 1 Reply Last reply Reply Quote 0
                      • beets
                        beets @Incremental last edited by

                        @incremental said in I need suggestions:

                        console.log("Test.vue - onInput() : ", file);

                        What does this line output?

                        I 1 Reply Last reply Reply Quote 0
                        • I
                          Incremental @beets last edited by

                          @beets it shows 91e20f71-529b-46c6-bf91-cf3179791666-image.png

                          beets 1 Reply Last reply Reply Quote 0
                          • beets
                            beets @Incremental last edited by

                            @incremental I had an error in the code, the onInput should look like:

                                onInput({ file }) {
                                  if (file instanceof File) {
                                    // Read the file, and convert to blob
                                    const reader = new FileReader()
                                    reader.onload = (e) => {
                                      this.newAvatar = {
                                        file,
                                        blob: e.target.result
                                      }
                                    }
                                    reader.readAsDataURL(file)
                                  }
                                }
                            

                            I’ve made a codesandbox: https://codesandbox.io/s/loving-glade-7eigi

                            I 1 Reply Last reply Reply Quote 0
                            • I
                              Incremental last edited by Incremental

                              and for the default picture, my store has it in the form :
                              this.$store.state.server.user.avatar_fullpath
                              so I tried :

                              	imageEl = createElement("img", {
                              		attrs: {
                              			draggable: false,
                              			//src: "http://example.com/default-image.png"
                              			src: this.$store.state.server.user.avatar_fullpath
                              		}
                              	});
                              

                              but It tells me the store is not defined…
                              … I use it in all my views !

                              beets 1 Reply Last reply Reply Quote 0
                              • beets
                                beets @Incremental last edited by

                                @incremental That component is a functional one, so this doesn’t exactly work there. But, that should be a default image anyway, as in the user has never selected an image to begin with.

                                I 1 Reply Last reply Reply Quote 0
                                • I
                                  Incremental @beets last edited by

                                  @beets Ok I understand as I’m still learning JS.
                                  Would it be possible to pass parameters to this component ?

                                  <user-avatar
                                     :image="avatar"
                                     default_image="image"
                                     :props.size="xx"
                                  />
                                  
                                  I 1 Reply Last reply Reply Quote 0
                                  • I
                                    Incremental @Incremental last edited by

                                    @beets : of course with props:…

                                    Thanks a lot for this fine code and your samples.

                                    To conclude, did you find problems with <q-file> for this ?

                                    beets 1 Reply Last reply Reply Quote 0
                                    • beets
                                      beets @Incremental last edited by

                                      @incremental Sorry for the delay, I assume you figured out passing a prop?

                                      I didn’t really find any problem inherent to using a q-file, it was just unnecessary in my case. The file-picker component could be replaced with a q-file component with some small modifications.

                                      1 Reply Last reply Reply Quote 0
                                      • I
                                        Incremental last edited by

                                        @beets no problem.
                                        No I haven’t tried the props yet, it was to understand the concepts…

                                        At this time, I am trying to use q-file, but I don’t find how to trigger when a user select or drop a file.
                                        Ideally, I’d like to display it in user-avatar.vue inside my profile.vue then send it to axios when the user click ‘update profile’

                                        I 1 Reply Last reply Reply Quote 0
                                        • I
                                          Incremental @Incremental last edited by

                                          @incremental
                                          I’ve done it with a mix of q-file and beets user.avatar.vue

                                          <user-avatar :image="avatar" />
                                          <q-file
                                          	v-model="modelAvatar"
                                          	style="max-width: 200px"
                                          	clearable
                                          	color="primary"
                                          	bottom-slots
                                          	accept=".jpg, image/*"
                                          	max-file-size="102400"
                                          	label="Ajoutez votre photo"
                                          	counter
                                          	@input="filePicked"
                                          	@clear="fileCleared"
                                          	@rejected="fileRejected"
                                          >
                                          

                                          Thanks a lot

                                          1 Reply Last reply Reply Quote 1
                                          • I
                                            Incremental @beets last edited by Incremental

                                            @beets Hello beets, sorry to annoy you again…
                                            I’d like to load my default avatar from my server and allow modify it with your onInput().
                                            onInput() works with a local file, but how could I load an image pointed by my profile avatar URL string ?
                                            If I pass my URL string to “file”, it doesn’t works with :

                                            if (file instanceof File) { }
                                            

                                            I saw a lot of code on the net, but I’m lost and don’t know what is the best and simpler…

                                            I beets 2 Replies Last reply Reply Quote 0
                                            • First post
                                              Last post