<!-- 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>
// 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);
};
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,
};
}
// 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;
});
<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>
// 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>
// 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]
},
}
/*
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 }
}