Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the possibility to specify css class to generated img html tag #145

Open
alxistn opened this issue Aug 12, 2021 · 11 comments
Open

Add the possibility to specify css class to generated img html tag #145

alxistn opened this issue Aug 12, 2021 · 11 comments

Comments

@alxistn
Copy link

alxistn commented Aug 12, 2021

Explain your use case

I want to be able to add a class to cld-image component that will be passed down to the generated element

Do you have a proposed solution?

Create a props to cld-image component named "imgClass" that will be concatenated to the rendered element

@patrick-tolosa
Copy link
Contributor

Hey @alxistn

You should be able to pass a class prop directly to the component, this will be merged into the tag classes

    <cld-image
        cloudName="demo"
        class="my-class-name"
        public-id="woman"
    >

This results in this HTML: (Abbreviated the TransformationURL for clarity)

<img cloudname="demo" class="my-class-name cld-image cld-image-loaded" src={TransformationURL}>

Is that what you had in mind?

@alxistn
Copy link
Author

alxistn commented Aug 12, 2021

Yes exactly !

Or to be more specific, because you could still want to style the container it could be something like that:

<cld-image
    cloudName="demo"
    class="high-level-class"
    imgClass="my-class-name-for-img"
    public-id="woman"
/>

and that will generate:

<div class="high-level-class">
    <img class="my-class-name-for-img" />
</div>

@alxistn
Copy link
Author

alxistn commented Aug 12, 2021

Here is an example implementation that could be done:

<script>
import { setup } from '../../mixins/setup';
import { compute } from '../../mixins/compute';
import { register } from '../../mixins/registerTransformation'
import { computePlaceholder } from '../../helpers/computeOptions'
import { getCldPlaceholder, isCldPlaceholder } from '../../helpers/findComponent'
import {
  ACCESSIBILITY_TRANSFORMATIONS,
  PLACEHOLDER_TRANSFORMATIONS,
  COMPONENTS,
  LAZY_LOADING,
  IMAGE_CLASSES,
  IMAGE_WITH_PLACEHOLDER_CSS,
  RESPONSIVE_CSS,
  PLACEHOLDER_CLASS,
  CLD_IMAGE_WRAPPER_CLASS
} from '../../constants';
import { size } from "../../mixins/size";
import { lazy } from "../../mixins/lazy";
import {getDevicePixelRatio} from '../../utils/getDevicePixelRatio';

/**
 * Deliver images and specify image transformations using the cld-image (CldImage) component,
 * which automatically generates an `<img>` tag including the dynamic URL of the image source.
 *
 *
 * You can optionally include [cld-transformation](#cldtransformation) components to define transformations to apply to the delivered image.
 *
 * For more information see the
 * <a href="https://cloudinary.com/documentation/vue_image_manipulation#cldvideo_component" target="_blank">
 * cld-image component</a> and
 * <a href="https://cloudinary.com/documentation/image_transformations#embedding_images_in_web_pages"
 * target="_blank">embedding images in web pages</a> documentation.
 */
export default {
  name: COMPONENTS.CldImage,
  mixins: [setup, compute, lazy, size, register],
  props: {
    /**
     * The css class that will be added to the <img> tag
     */
    imgClass: {
      type: String,
      default: ""
    },
    /**
     * The unique identifier of an uploaded image.
     */
    publicId: { type: String, default: "", required: true },
    /**
     * Whether to generate a JPEG using the [progressive (interlaced) JPEG
     * format](https://cloudinary.com/documentation/transformation_flags#delivery_and_image_format_flags).
     */
    progressive: {
      type: Boolean,
      default: false
    },
    /**
     * **Deprecated**
     *
     * The placeholder image to use while the image is loading. Possible values:
     * - `"blur"` to use blur placeholder
     * - `"lqip"` to use a low quality image
     * - `"color"` to use an average color image
     * - `"pixelate"` to use a pixelated image
     * - `"vectorize"` to use a vectorized image
     * - `"predominant-color" to use a predominant color image
     * @deprecated - Use CldPlaceholder instead
     */
    placeholder: {
      type: String,
      default: "",
      validator: value => !value || !!PLACEHOLDER_TRANSFORMATIONS[value]
    },

    /**
     * Out-of-the-box support for accessibility mode, including colorblind and dark/light mode
     */
    accessibility: {
      type: String,
      default: "",
      validator: value => !value || !!ACCESSIBILITY_TRANSFORMATIONS[value]
    }
  },
  data() {
    return {
      imageLoaded: false,
      cloudinary: null,
    }
  },
  methods: {
    load() {
      this.imageLoaded = true;
    },
    renderImageOnly(src, hasPlaceholder = false) {
      const imgClass = `${IMAGE_CLASSES.DEFAULT} ${!this.imageLoaded ? IMAGE_CLASSES.LOADING : IMAGE_CLASSES.LOADED} ${this.imgClass}`
      const style = {
        ...(this.responsive ? RESPONSIVE_CSS[this.responsive] : {}),
        ...(!this.imageLoaded && hasPlaceholder ? IMAGE_WITH_PLACEHOLDER_CSS[IMAGE_CLASSES.LOADING] : {})
      }

      return (
        <img
          attrs={this.$attrs}
          src={src}
          loading={this.hasLazyLoading ? LAZY_LOADING : null}
          class={imgClass}
          onload={this.load}
          style={style}
        />
      )
    },
    renderComp(children) {
      this.setup(this.$attrs)

      if (this.placeholder) {
        // eslint-disable-next-line
        console.warn ('The prop "placeholder" has been deprecated, please use the cld-placeholder component');
      }

      const responsiveModeNoSize = this.responsive && !this.size
      const lazyModeInvisible = this.hasLazyLoading && !this.visible
      const options = this.computeURLOptions()

      let src = responsiveModeNoSize || lazyModeInvisible ? '' : this.cloudinary.url(this.publicId, this.toSnakeCase(options));
      // Update dpr_auto to dpr_1.0, 2.0 etc but only for responsive mode
      // This matches the behaviour in other SDKs
      if (this.responsive) {
        src = src.replace(/\bdpr_(1\.0|auto)\b/g, 'dpr_' + getDevicePixelRatio(true));
      }

      const cldPlaceholder = getCldPlaceholder(children)
      const cldPlaceholderType = cldPlaceholder ? (cldPlaceholder.componentOptions?.propsData?.type || 'blur') : ''
      const placeholderType = cldPlaceholderType || this.placeholder

      const placeholderOptions = placeholderType ? this.toSnakeCase(computePlaceholder(placeholderType, options)) : null

      if (!placeholderOptions) {
        return this.renderImageOnly(src)
      }

      const placeholder = responsiveModeNoSize ? '' : this.cloudinary.url(this.publicId, placeholderOptions)
      const displayPlaceholder = !this.imageLoaded && placeholder

      return (
        <div class={CLD_IMAGE_WRAPPER_CLASS}>
        { this.renderImageOnly(src, true) }
        { displayPlaceholder && (
          <img
              src={placeholder}
              attrs={this.$attrs}
              class={PLACEHOLDER_CLASS}
              style={IMAGE_WITH_PLACEHOLDER_CSS[PLACEHOLDER_CLASS]}
            />) }
        </div>
      )
    }
  },
  render(h) {
    if (!this.publicId) return null

    const children = this.$slots.default || []
    const hasExtraTransformations = children.length > 1 || (children.length === 1 && !isCldPlaceholder(children[0]))

    /* Render the children first to get the extra transformations (if there is any) */
    if (hasExtraTransformations && !this.extraTransformations.length) {
      return h(
        "img", {
          attrs: this.attrs
        },
        this.$slots.default
      );
    }

    return this.renderComp(children)
  }
};
</script>

Here there is a new prop named "imgClass" of type String and default to an empty string.

And then when creating the and its class you concatenate the "imgClass" prop with the existing cloudinary classes like this:

const imgClass = `${IMAGE_CLASSES.DEFAULT} ${!this.imageLoaded ? IMAGE_CLASSES.LOADING : IMAGE_CLASSES.LOADED} ${this.imgClass}`

@eyalktCloudinary
Copy link

eyalktCloudinary commented Aug 12, 2021

Hey @alxistn, the cld-image component returns an <img> element (without a wrapping <div>).
If you want to wrap it you can place it inside a div -

<div class="high-level-class">
    <cld-image
      cloudName="demo"
      class="my-class-name-for-img"
      public-id="woman"
      />
</div>

which would resolve to -

<div class="high-level-class">
    <img cloudname="demo" 
         class="my-class-name-for-img cld-image cld-image-loaded" 
         src={TransformationURL}>
</div>

This looks like what you are looking for - #145 (comment)

Another thing to note is that if you do wrap the cld-image component with a div element that has a class (like the above example), you could just reference the image in CSS with the .high-level-class img {...} selector (in that case you do not need to specify a class in the cld-image component).

@alxistn
Copy link
Author

alxistn commented Aug 12, 2021

Well try it yourself you will see it does render the tag inside a

.

@patrick-tolosa
Copy link
Contributor

@alxistn it depends on how you use the component, for example a simple rendering as I've shown above:

    <cld-image
        cloudName="demo"
        class="my-class-name"
        public-id="woman"
    >

Generates this (full) html - without a div

<img cloudname="demo" class="my-class-name cld-image cld-image-loaded" src={TransformationURL}>

However, if you're using a placeholder an example, you are indeed getting a wrapping div, with this HTML

<div class="my-class-name cld-image-wrapper" cloudname="demo">
<img cloudname="demo" src="{TransformationURL}" class="cld-image cld-image-loaded" style="">
</div>

Note how in this case, only the top-most HTML element receives the className (So just the div, but not the img)

Given this information, can you explain what exactly you'd like to achieve?

@alxistn
Copy link
Author

alxistn commented Aug 12, 2021

Given this I would need this result:

<div class="my-class-name cld-image-wrapper" cloudname="demo">
<img cloudname="demo" src="{TransformationURL}" class="cld-image cld-image-loaded my-class-name" style="">
</div>

@patrick-tolosa
Copy link
Contributor

Thanks @alxistn , now it's understood.

Although this goes against the way Vue works (The top-most component always gets the class passed to the component), this does sound like something that can help make things more consistent.

It looks like the best approach would be to add a new prop specifically for the img tag.
Our team is currently focused on our new generation of the SDKs (including the major Vue release), however, if you can open a PR for this issue, we'll be happy to review and assist in merging it.

@alxistn
Copy link
Author

alxistn commented Aug 16, 2021

Great ! This is what I had in mind but I'm not sure how to process given that I don't have the rights to do so

@patrick-tolosa
Copy link
Contributor

@alxistn You should be able to fork the project (Top right in your github UI), once you've forked the repo, you can commit to your fork copy your changes, then you can open a PR from the fork to this repository.

Once the PR is open we'll get a notification and we can start the review process.

@alxistn
Copy link
Author

alxistn commented Aug 27, 2021

@patrick-tolosa thanks for the explanation. I was able to submit the PR 👌 (#148)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants