I have this Vue Material (Vue.js) tag, with the function
<md-button id="" v-on:click.native="requestSelected(request)">
methods: {
requestSelected: function(request) {
request.accepted = true;
console.log(request);
var card = document.getElementById('text');
var accept = document.createTextNode("Job selected");
card.appendChild(accept);
}
I'm trying to add some text on the DOM after clicking, could someone recommend to me some Vue js documentacion to check info please
In your Vue component, create a data property for your display text:
data() {
return {
displayText: '',
}
}
Then, just put a reference to displayText in your template like so:
{{ displayText }}
Vue will initially display nothing, since displayText is empty, and the automatically update the DOM when displayText changes.
You would change the text in the requestSelected method like so:
requestSelected: function(request) {
request.accepted = true;
this.displayText = "Job selected";
}
Here's an example in codepen.
Related
I want to implement the autocomplete search (the one on the left) from this codepen to pure web components. But something went wrong because slots don't work and something else also doesn't work but I can't figure out what it is. What I have so far
Search-select
const template = `
<p>
<slot name="autocomp" results="${this.results}"
searchList="${(event) => this.setQuery(event)}"
>
fgfgfg
</slot>
yo
</p>
`;
class SearchSelect extends HTMLElement {
constructor() {
super();
this.query = "";
this.results = [];
this.options = [
"Inside Out",
"John Wick",
"Jurassic World",
"The Lord of the Rings",
"Pacific Rim",
"Pirates of the Caribbean",
"Planet of the Apes",
"Saw",
"Sicario",
"Zombies",
];
this.shadow = this.attachShadow({ mode: "open" });
}
setQuery(event) {
console.log(event.target);
this.query = event.target.value;
}
get options() {
return this.getAttribute("options");
}
set options(val) {
this.setAttribute("options", val);
}
static get observedAttributes() {
return ["options", "filterMethod"];
}
filterMethod(options, query) {
return options.filter((option) =>
option.toLowerCase().includes(query.toLowerCase())
);
}
attributeChangedCallback(prop, oldValue, newValue) {
if (prop === "options") {
this.results = this.filterMethod(this.options, this.query);
this.render();
}
if (prop === "filterMethod") {
this.results = this.filterMethod(this.options, this.query);
this.render();
}
}
render() {
this.shadow.innerHTML = template;
}
connectedCallback() {
this.render();
}
}
customElements.define("search-select", SearchSelect);
Autocomplete
const templ = `
<search-select>
<div class="autocomplete">
<input
type="text"
placeholder="Type to search list"
onchange="${this.searchList}"
onfocus="${this.showDropdown}"
onblur="${this.hideDropdown}"
/>
<div class="autocomplete-dropdown" v-if="dropdownVisible">
<ul class="autocomplete-search-results-list">
${this.result}
</ul>
</div>
</div>
</search-select>
`;
class Autocomplete extends HTMLElement {
constructor() {
super();
this.dropdownVisible = false;
this.rslts = "";
this.shadow = this.attachShadow({ mode: "open" });
}
get results() {
return this.getAttribute("results");
}
set results(val) {
this.setAttribute("results", val);
}
get searchList() {
return this.getAttribute("searchList");
}
showDropdown() {
this.dropdownVisible = true;
}
hideDropdown() {
this.dropdownVisible = false;
}
attributeChangedCallback(prop, oldValue, newValue) {
this.render();
}
render() {
this.shadow.innerHTML = templ;
}
connectedCallback() {
this.render();
}
}
customElements.define("auto-complete", Autocomplete);
Your current approach is completely wrong. Vue is reactive framework. Web components do not provide reactivity out of box.
The translation of Vue2 component to direct Web component is not straight forward. The slots do not work because Vue.js slots are not the same as Web component slots. They are just conceptually modeled after them.
First, when you use the Vue.js slot, you are practically putting some part of the vDOM (produced as a result of JSX) defined by the calling component into the Search or Autocomplete component. It is not a real DOM. Web components, on the other hand, provide slot which actually accepts a real DOM (light DOM).
Next, your render method is practically useless. You are simply doing this.shadow.innerHTML = template; which will simply append the string as HTML into the real DOM. You are not resolving the template nodes. Vue.js provides a reactivity out of box (that's why you need Vue/React). Web components do not provide such reactivity. On each render, you are re-creating entire DOM which is not a good way to do it. When you are not using any framework to build web component, you should construct all the required DOM in connectedCallback and then keep on selectively updating using DOM manipulation API. This is imperative approach to building UIs.
Third, you are using named slot while consuming it in auto complete, you are not specifying the named slot. So whatever is the HTML you see is not getting attached to the Shadow DOM.
You will need to
Building a complex component like Auto Complete needs a basic reactivity system in place that takes care of efficiently and automatically updating the DOM. If you do not need full framework, consider using Stencil, LitElement, etc. If you can use Vue.js, just use it and wrap it into Web component using helper function.
For Vue 2, you can use the wrapper helper library. For Vue 3, you can use the built-in helper.
In the codepen below I have a Element-UI datepicker set up to show a dynamic disabled dates based on a random number.
The number of disabled dates change every time the datepicker input comes into focus.
My issue is the datepicker doesn't refresh the disabled dates until you click on a different month. The datepicker also shows the last month you were previously on when when you click off and back in.
Is there a way to force Element-UI Datepicker to refresh? I would like to make the datepicker refresh in the on focus event after the new disabled value is set.
https://codepen.io/anon/pen/rbXjLr
Element-UI Datepicker Documentation
<div id="app">
<template>
<div class="block">
<span class="demonstration">Picker with quick options</span>
<el-date-picker
v-model="value2"
type="date"
placeholder="Enter Date"
#focus="focus()"
:default-value="defaultValue"
:picker-options="pickerOptions">
</el-date-picker>
</div>
</template>
</div>
var is10Days = "";
var Randomizer = (function(){
var is10DaysSetter = function(){
if(is10Days === "") {
is10Days = Math.round(Math.random()) === 1;
}
//console.log("is10Days: " + is10Days);
return is10Days;
}
return {
Is10DaysSetter: is10DaysSetter
}
})()
var Main = {
data() {
return {
defaultValue: "",
pickerOptions: {
disabledDate(time) {
var self = this;
var date = moment()._d;
var mindate = moment().subtract(5,'d')._d;
var maxDate = moment()._d;
var isBeforeMinDate = time.getTime() < mindate;
var isAfterMaxDate = time.getTime() > maxDate;
if(is10Days !== "" && is10Days){
var alternateMinDate = date.setDate(date.getDate() - 10);
isBeforeMinDate = time.getTime() < alternateMinDate;
}
//console.log("disabledDate");
return isBeforeMinDate || isAfterMaxDate;
}
},
value2: '',
};
},
methods:{
focus: function() {
var self = this;
is10Days = "";
self.defaultValue = moment().format("YYYY-MM-DD");
Randomizer.Is10DaysSetter();
console.log("reset is10Days: " + (is10Days ? "10 days" : "5 days"));
}
}
};
var Ctor = Vue.extend(Main)
ELEMENT.locale(ELEMENT.lang.en)
new Ctor().$mount('#app')
I posted this as a feature request on Element UI's git hub and received a response:
https://github.com/ElemeFE/element/issues/15380
<el-date-picker
ref="picker" //Added this
v-model="value2"
type="date"
placeholder="Enter Date"
#focus="focus()"
:picker-options="pickerOptions">
</el-date-picker>
methods:{
focus: function() {
var self = this;
is10Days = "";
Randomizer.Is10DaysSetter();
//Added this
this.$nextTick(_ => {
this.$refs.picker.picker.date = new Date()
})
console.log("reset is10Days: " + (is10Days ? "10 days" : "5 days"));
}
}
Adding a reference to picker allowed me to override the unwanted feature of going back to the previously viewed month and solved my issue. This came with a warning that since this is not part of the public API, it could change in a future version.
Here is a link to a working code pen:
https://codepen.io/steveshore/pen/rbXjLr
I think it is about compomemt render. when your main vue app initialize, <el-date-picker>
rendered completly first.
The problem is when date-picker finished rendering, are the main vue datas ready?
It seems you pass a null into options, but select another month will force update options.
could you try this?
How to Initialize Data Properties with Prop Values
make a v-if="pickerOptions" in attr
I'm creating a form using vue.js and I need to create inputs in vue that is always capitalized. I know I could use the css property
text-transform: uppercase;
and then transform the data before sending using
data.someData.toUpperCase()
But I wonder if there is a more intelligent way of doing that in vue.js. In react we can create controlled inputs and easily do it. Is there anything like that in Vue.js?
I managed to do it using computed fields, however, I would have to create computed getter and setter for each input in the form. Is there a better way of doing it?
You could create a custom directive.
Vue.directive( 'touppercase', {
update (el) {
el.value = el.value.toUpperCase()
},
})
And then use it where you need. For example:
<input type="text" v-model="modelfield" v-touppercase>
Since you don't have a lot of code to run, you should manually bind events to your textfield and then handle the uppercasing there.
Handling events from a text field can be done by adding an input event handler on them, and then updating the initial state again.
<input :value="text" #input="updateText($event.target.value)"/>
export default {
data() {
return {
text: '',
}
},
methods: {
updateText(newValue) {
this.value = newValue.toUpperCase();
},
}
}
You can also do it inline in a template, but this might make it harder to read depending on your code style preferences
<input :value="text" #input="text = $event.target.value.toUpperCase()"/>
This directive works fine with v-model (last character is in upper case too):
Vue.directive('uppercase', {
update(el) {
const sourceValue = el.value;
const newValue = sourceValue.toUpperCase();
if (sourceValue !== newValue) {
el.value = newValue;
el.dispatchEvent(new Event('input', { bubbles: true }));
}
},
});
Usage:
<input type="text" v-model="myField" v-uppercase />
You can do this:
<input :value="theValue" #input="theValue = theValue.toUpperCase()"/>
as a fix for asologor's answer you should reach input element to change it at vuetify
Vue.directive("uppercase", {
update(el) {
const sourceValue = el.getElementsByTagName("input")[0].value
const newValue = sourceValue.toUpperCase()
if (sourceValue !== newValue) {
el.getElementsByTagName("input")[0].value = newValue
el = el.getElementsByTagName("input")[0]
el.dispatchEvent(new Event("input", { bubbles: true }))
}
}
})
and usage is
<v-text-field
type="text"
outlined
placeholder="placeholder"
v-model="name"
prepend-inner-icon="edit"
v-uppercase
/>
this is defnetely working
I need to create a checkbox that will be checked/unchecked depending on the value of a parameter coming from the database.
I'm not able to load that value when I'm rendering the page, so the idea is: render the page, "tell" the checkbox to "ask" the server what is the current value of the parameter and then check/uncheck the checkbox depending on the response. Then, if the user checks/unchecks the checkbox, make a new Ajax request to update the value in the database.
I wrote some code (I'm new in Vuejs, so for sure I'm doing something wrong):
var vm = new Vue({
el: '#root',
computed: {
checked() {
return this.initialize()
},
value() {
return this.checked
}
},
watch: {
checked() {
alert('watcher')
this.update();
}
},
methods: {
initialize(){
// Just pretending an initial value
var randomBoolean = Math.random() >= 0.5;
alert('Ajax request here to initialize it as ' + (randomBoolean ? 'checked' : 'unchecked'));
return randomBoolean;
},
update(){
alert('ajax request here to set it to ' + this.value)
}
}
});
You can check and run the code here: https://jsfiddle.net/hyn9Lcv2/
Basically it works to initialize the checkbox, but then it fails to update. If you check the console, there is this error:
[Vue warn]: Computed property "checked" was assigned to but it has no setter.
First have you thought of using the created() hook from the vue instance instead of watcher?
It's recommended and will execute the code as soon as the component is created.
From the doc:
new Vue({
data: {
a: 1
},
created: function () {
//Ajax call:
//onsuccess(response){
this.a = reponse.data.a
}
}
})
in the created hook you can do your ajax call, (axios is good library for that, worth checking it out: https://github.com/axios/axios ).
Then from your ajax response you can link the desired value to your checkbox by assigning it to a variable in the data object of the instance (in our case 'a')
Then bind it to your checkbox with the v-model like this:
<input
type="checkbox"
v-model="a">
I recommend to check the vue doc for more info on biding: https://v2.vuejs.org/v2/guide/forms.html#Checkbox-1
Hope it helps.
Just add bind click event
<div id="root">
<input id="check" type="checkbox" name="active" v-model="checked" #click="update">
<label for="check">Click me</label>
</div>
You need to fetch the database value when the component is created or mounted.
You then need to bind your checkbox with the initialized data.
Finally you need to watch the data to send an update to the database.
var vm = new Vue({
el: '#root',
data: {
//Your data
checked: null
},
// Function where you are going to fetch your data
mounted: function () {
console.log("Ajax call to initialize");
this.checked = Math.random() >= 0.5;
},
watch: {
// Watcher to save your data in the database
checked: function(newValue, oldValue){
if (oldValue === null) { return; } // to not make an useless update when data has been fetched
console.log("Ajax call to update value " + newValue);
}
}
});
<div id="root">
<input id="check" type="checkbox" name="active" v-model="checked" :disabled="checked === null">
<label for="check">Click me</label>
</div>
To fetch your data you can use for example Axios that works great with Vue.
To know more about life cycle of a component (to know if you should do the fetching at created or mounted) : https://v2.vuejs.org/v2/guide/instance.html
I created a custom select2 input element for Vue 2.
My question is: why is
<select2 v-model="vacancy.staff_member_id" #input="update(vacancy)"></select2>
working, but
<select2 v-model="vacancy.staff_member_id" #change="update(vacancy)"></select2>
not?
Since normal <input> elements in Vue have a #change handler, it would be nice if my custom select2 input has the same.
Some information on my custom element:
The purpose of this element is to not render all <option> elements but only those needed, because we have many select2 inputs on one page and many options inside a select2 input, causing page load to become slow.
This solution makes it much faster.
Vue.component('select2', {
props: ['options', 'value', 'placeholder', 'config', 'disabled'],
template: '<select><slot></slot></select>',
data: function() {
return {
newValue: null
}
},
mounted: function () {
var vm = this;
$.fn.select2.amd.require([
'select2/data/array',
'select2/utils'
], function (ArrayData, Utils) {
function CustomData ($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
if (params.term && params.term !== '') {
// search for term
var results;
var termLC = params.term.toLowerCase();
var length = termLC.length;
if (length < 3) {
// if only one or two characters, search for words in string that start with it
// the string starts with the term, or the term is used directly after a space
results = _.filter(vm.options, function(option){
return option.text.substr(0,length).toLowerCase() === termLC ||
_.includes(option.text.toLowerCase(), ' '+termLC.substr(0,2));
});
}
if (length > 2 || results.length < 2) {
// if more than two characters, or the previous search give less then 2 results
// look anywhere in the texts
results = _.filter(vm.options, function(option){
return _.includes(option.text.toLowerCase(), termLC);
});
}
callback({results: results});
} else {
callback({results: vm.options}); // no search input -> return all options to scroll through
}
};
var config = {
// dataAdapter for displaying all options when opening the input
// and for filtering when the user starts typing
dataAdapter: CustomData,
// only the selected value, needed for un-opened display
// we are not using all options because that might become slow if we have many select2 inputs
data:_.filter(vm.options, function(option){return option.id === parseInt(vm.value);}),
placeholder:vm.placeholder
};
for (var attr in vm.config) {
config[attr] = vm.config[attr];
}
if (vm.disabled) {
config.disabled = vm.disabled;
}
if (vm.placeholder && vm.placeholder !== '') {
$(vm.$el).append('<option></option>');
}
$(vm.$el)
// init select2
.select2(config)
.val(vm.value)
.trigger('change')
// prevent dropdown to open when clicking the unselect-cross
.on("select2:unselecting", function (e) {
$(this).val('').trigger('change');
e.preventDefault();
})
// emit event on change.
.on('change', function () {
var newValue = $(this).val();
if (newValue !== null) {
Vue.nextTick(function(){
vm.$emit('input', newValue);
});
}
})
});
},
watch: {
value: function (value, value2) {
if (value === null) return;
var isChanged = false;
if (_.isArray(value)) {
if (value.length !== value2.length) {
isChanged = true;
} else {
for (var i=0; i<value.length; i++) {
if (value[i] !== value2[i]) {
isChanged = true;
}
}
}
} else {
if (value !== value2) {
isChanged = true;
}
}
if (isChanged) {
var selectOptions = $(this.$el).find('option');
var selectOptionsIds = _.map(selectOptions, 'value');
if (! _.includes(selectOptionsIds, value)) {
var missingOption = _.find(this.options, {id: value});
var missingText = _.find(this.options, function(opt){
return opt.id === parseInt(value);
}).text;
$(this.$el).append('<option value='+value+'>'+missingText+'</option>');
}
// update value only if there is a real change
// (without checking isSame, we enter a loop)
$(this.$el).val(value).trigger('change');
}
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
The reason is because you are listening to events on a component <select2> and not an actual DOM node. Events on components will refer to the custom events emitted from within, unless you use the .native modifier.
Custom events are different from native DOM events: they do not bubble up the DOM tree, and cannot be captured unless you use the .native modifier. From the docs:
Note that Vue’s event system is separate from the browser’s EventTarget API. Though they work similarly, $on and $emit are not aliases for addEventListener and dispatchEvent.
If you look into the code you posted, you will see this at the end of it:
Vue.nextTick(function(){
vm.$emit('input', newValue);
});
This code emits a custom event input in the VueJS event namespace, and is not a native DOM event. This event will be captured by v-on:input or #input on your <select2> VueJS component. Conversely, since no change event is emitted using vm.$emit, the binding v-on:change will never be fired and hence the non-action you have observed.
Terry pointed out the reason, but actually you can simply pass your update event to the child component as a prop. Check demo below.
Vue.component('select2', {
template: '<select #change="change"><option value="value1">Value 1</option><option value="value2">Value 2</option></select>',
props: [ 'change' ]
})
new Vue({
el: '#app',
methods: {
onChange() {
console.log('on change');
}
}
});
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<div>
<p>custom select</p>
<select2 :change="onChange"></select2>
</div>
<div>
<p>default select</p>
<select #change="onChange">
<option value="value1">Value 1</option>
<option value="value2">Value 2</option>
</select>
</div>
</div>
fiddle