/ Gists / Vue.js

Gists - Vue.js

On gists

defineProps - Vue (and reactive behavior)

Popular ⭐ Vue.js

parent.vue #

<script setup>
import  Child  from './Child.vue'
import { ref } from 'vue'

const test = ref('one')

setTimeout(() => {
  test.value = 'two'
}, 3000)

</script>

<template>
  <h1>Parent</h1>
  <Child :test="test"/>
</template>

On gists

v-model, defineModel, composition api

Popular ⭐ Vue.js

example.vue #

<!-- Parent -->
<template>
  <CommonInput v-model="inputValue" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const inputValue = ref();
</script>


<!-- Child -->
<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup lang="ts">
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
</script>


<!-- **************** With defineModel **************** -->
<!-- https://javascript.plainenglish.io/understanding-vue-3s-definemodel-for-two-way-binding-streamline-your-code-with-this-guide-ac970f365e4a -->
<!-- Parent -->
<template>
  <CommonInput v-model="inputValue" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const inputValue = ref();
</script>


<!-- Child -->
<template>
  <input v-model="model" />
</template>

<script setup lang="ts">
// declares "modelValue" prop, consumed by parent via v-model
const model = defineModel();
// emits "update:modelValue" when mutated
model.value = "hello";
</script>




<!-- **************** Without defineModel **************** -->
<!-- Child -->
<template>
  <input v-model="props.modelValue" />
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';

const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
</script>


<!-- OR with computed, probably better-->

<!-- Child -->
<template>
  <input v-model="inputValue" />
</template>

<script setup lang="ts">
import { computed } from 'vue';

const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);

const inputValue = computed({
  get: () => props.modelValue,
  set: (value) => emit('update:modelValue', value),
});
</script>

On gists

Configuration options object

Popular ⭐ JavaScript Vue.js

options.js #

// JS
function setPreferences({ theme = 'dark', layout = 'grid' } = {}) {
  console.log(`Preferences set: Theme - ${theme}, Layout - ${layout}`);
}

export function useRefHistory(ref, options) {
  const {
    deep = false,
    capacity = Infinity,
  } = options;

  // ...
};

// https://michaelnthiessen.com/tips/options-object
export function useRefHistory(ref, options) {
  const {
    deep = false,
    capacity = Infinity,
    ...otherOptions,
  } = options;

  // Pass along some options we're not using directly
  useSomeOtherComposable(otherOptions);
};

On gists

Vue composition - theme selector

Vue.js

ThemeSelection.js #

import { ref, computed } from 'vue';

const theme = ref('light');

export function useThemeSelection() {
    const getTheme = () => theme.value;
    const setTheme = v => {
        theme.value = v;
    };

    const selectedTheme = computed({
        get: () => getTheme(),
        set: newTheme => setTheme(newTheme),
    });

    return {
        theme,
        selectedTheme,
    };
}

On gists

Importing JSON file

JavaScript Vue.js

foo.js #

// ES 6, JS without bundlers

// 1) fetch('data.json').then().then() ...
// 2) import data from './data.json' assert { type: 'json' };


// Vue
async function fetchData() {
    try {
        const response = await import('@/data.json');
        const data = await response.default; // Získání dat z Promise
        console.log(data);
    } catch (error) {
        console.error('Chyba při načítání dat:', error);
    }
}


// Or
const o = ref([]);
const data = import('@/data.json').then(x => {
    o.value = x.default;
});

On gists

Groupping computed & methods (very limited)

Vue.js

example.vue #

<template>
  <p>{{ counterInfo.currentValue }} / {{ counterInfo.nextValue }}</p>
  <button @click="pokus().a(50)">Pokus</button>
</template>

<script>
      methods: {
        pokus() {
            return {
                a: n => {
                    this.counter = n;
                },
            };
        },
    },
    computed: {
        counterInfo() {
            return {
                currentValue: this.counter,
                nextValue: this.counter + 1,
                // tohle nejde :D
                beforeValue: () => {
                    return this.counter - 1;
                },
            };
        },
    },
  
</script>

On gists

v-model (ukazky implementaci)

Vue.js

App.vue #

<template>
  <div id="app">
    Data z rodiče: {{ msg }}
    <hr />
    <Formik v-model="msg" />
  </div>
</template>

<script>
import Formik from './components/Formik.vue';

export default {
  name: 'App',
  components: {
    Formik,
  },
  data() {
    return {
      msg: 'pica2',
    };
  },
};
</script>

On gists

Vue.js design pattern

JS Patterns Vue.js

pattern.js #

// https://blog.logrocket.com/exploring-advanced-design-patterns-vue-js/
// + MORE ... builder, adapter more usable ...

// BUILDER

// FormBuilder.js
class FormBuilder {
  constructor() {
    this.formFields = [];
  }
  addTextField(name, label, placeholder = '') {
    this.formFields.push({
      type: 'text',
      name,
      label,
      placeholder,
    });
    return this; // Return the builder instance for method chaining
  }
  addNumberField(...) {
    // Add a number field
  }
  addSelectField(...) {
    // Add a select field
  }
  build() {
    return this.formFields;
  }
}
export default FormBuilder;


//  Form.vue 
import FormBuilder from './helpers/FormBuilder';

export default {
  data() {
    return {
      formFields: [],
      formData: {}, // Add a data property to store the form data
    };
  },
  created() {
    /* 
     * Use the FormBuilder class and its methods 
     * to construct the `formFields` object array 
     * with different form fields.
     */
    this.formFields = new FormBuilder()
      .addTextField('name', 'Name')
      .addNumberField(...)
      .addSelectField(...)
      .build();
  },
  methods: {
    handleSubmit() {
      // Log the form data when the form is submitted
      console.log(this.formData);
    },
  },
};



// ADAPTER
// UserAdapter.js
class UserAdapter {
  constructor(externalUserData) {
    this.externalUserData = externalUserData;
  }
  adapt() {
    /*
     * Adapt the external user data to match
     * the component's expected format.
     *
     * Considering the structure of the expected
     * response, grab the right object array
     * (`results`) and its only value.
     *
     * @link https://randomuser.me/api/
     */
    const userData = this.externalUserData.results[0];
    return {
      name: `${userData.name.first} ${userData.name.last}`,
      email: userData.email,
      gender: userData.gender,
      location: `${userData.location.city}, ${userData.location.country}`,
      displayPic: userData.picture.large,
    };
  }
}
export default UserAdapter;

import UserAdapter from './UserAdapter';

export default {
  data() {
    return {
      userAdapter: null,
      user: null
    };
  },
  created() {
    /*
     * Get user data from an external API 
     * with a different format.
     */
    async created() {
      try {
        // Make an API call
        const response = await fetch('...');
        if (!response.ok) {
          throw new Error('Failed to fetch external user data');
        }
        const externalUserData = await response.json();

        /*
         * Create an adapter to convert external weather data 
         * to the format expected by the component.
         */
        this.userAdapter = new UserAdapter(externalUserData);

        // Adapt the data to the component's expected format
        this.user = this.userAdapter.adapt();
      } catch (error) {
        console.error('Error fetching external user data:', error);
        this.errorMessage = 'Failed to load user data. Please try again later.';
      }
    },
  }
};




// Composable pattern
// userPreferences.js
import { ref } from 'vue';

const theme = ref('light');
const language = ref('english');
const notifications = ref(true);

// Getter for theme
const getTheme = () => theme.value;

// Setter for theme
const setTheme = (newTheme) => {
  theme.value = newTheme;
};

// Getter for language
const getLanguage = () => language.value;

// Setter for language
const setLanguage = (newLanguage) => {
  language.value = newLanguage;
};

// Getter for notifications
const getNotificationsEnabled = () => notifications.value;

// Setter for notifications
const setNotificationsEnabled = (enabled) => {
  notifications.value = enabled;
};

export {
  getTheme,
  setTheme,
  getLanguage,
  setLanguage,
  getNotificationsEnabled,
  setNotificationsEnabled,
};


// themeSelector.js
import { computed } from 'vue';
import { getTheme, setTheme } from '../utils/userPreferences';

export function useThemeSelection() {
  const selectedTheme = computed({
    get: () => getTheme(),
    set: (newTheme) => setTheme(newTheme),
  });
  return {
    selectedTheme,
  };
}

// ThemeSelector.vue
<template>
  <div>
    <label for="theme-switch">Theme</label>
    <select id="theme-switch" @change="handleChange" v-model="selectedTheme">
      <option value="light">Light</option>
      <option value="dark">Dark</option>
    </select>
  </div>
</template>
<script>
import { defineComponent } from 'vue';
import { useThemeSelection } from '../composables/themeSelector';

export default defineComponent({
  setup() {
    const { selectedTheme } = useThemeSelection();
    const handleChange = (event) => {
      const newTheme = event.target.value;
      selectedTheme.value = newTheme; // Set the new theme
    };
    return {
      selectedTheme,
      handleChange,
    };
  },
});
</script>


// AnyOther.vue
<template>
  <div class="settings">
    <div class="settings-row">
      <ThemeSelector />
      <div :class="selectedTheme">
        Here's how the {{ selectedTheme }} theme looks like.
      </div>
    </div>
  </div>
</template>

<script>
import { defineComponent } from 'vue';
import { useThemeSelection } from './composables/themeSelector';
import ThemeSelector from './components/ThemeSelector';

export default defineComponent({
  setup() {
    const { selectedTheme } = useThemeSelection();

    return {
      selectedTheme,
    };
  },
  components: {
    ThemeSelector
  },
});
</script>

<style>
.light {
  background-color: #fff;
  color: #000;
}
.dark {
  background-color: #000;
  color: #fff;
}
</style>

On gists

Better prev/next index in array

JavaScript Vue.js

app.js #

// after our data inside the Vue instance
  computed: {
    currentImage () {
      return images[this.currentImageIndex]
    },
    previousImageIndex () {
      return (this.currentImageIndex - 1 + images.length) % images.length
    },
    previousImage () {
      return images[this.previousImageIndex]
    },
    nextImageIndex () {
      return (this.currentImageIndex+1) % images.length
    },
    nextImage () {
      return images[this.nextImageIndex]
    },
  }

On gists

Input validation composable

Vue.js

validation.js #

/*
vylepseny priklad o nazev pro v-model 
*/


import { ref, watch } from 'vue'

export function useFormValidation(initialValue, propName = 'value') {
  const value = ref(initialValue)
  const error = ref('')

  watch(value, (newValue) => {
    if (newValue.length < 3) {
      error.value = 'Value must be at least 3 characters'
    } else {
      error.value = ''
    }
  })

  function validate() {
    if (value.value.length < 3) {
      error.value = 'Value must be at least 3 characters'
    } else {
      error.value = ''
    }
  }

  return { [propName]: value, error, validate }
}