I created a custom directive to handle select2 in VueJs. The code below works when I am binding a select to a data property in my viewmodel that is not a propert of an object within data.
Like this.userId but if it is bound to something like this.user.id, it would not update the value in my viewmodel data object.
Vue.directive('selected', {
bind: function (el, binding, vnode) {
var key = binding.expression;
var select = $(el);
select.select2();
vnode.context.$data[binding.expression] = select.val();
select.on('change', function () {
vnode.context.$data[binding.expression] = select.val();
});
},
update: function (el, binding, newVnode, oldVnode) {
var select = $(el);
select.val(binding.value).trigger('change');
}
});
<select v-selected="userEditor.Id">
<option v-for="user in users" v-bind:value="user.id" >
{{ user.fullName}}
</option>
</select>
Related fiddle:
https://jsfiddle.net/raime910/rHm4e/4/
When you using 1st level $data's-property, it accessing to $data object directly through []-brackets
But you want to pass to selected-directive the path to nested object, so you should do something like this:
// source: https://stackoverflow.com/a/6842900/8311719
function deepSet(obj, value, path) {
var i;
path = path.split('.');
for (i = 0; i < path.length - 1; i++)
obj = obj[path[i]];
obj[path[i]] = value;
}
Vue.directive('selected', {
bind: function (el, binding, vnode) {
var select = $(el);
select.select2();
deepSet(vnode.context.$data, select.val(), binding.expression);
select.on('change', function () {
deepSet(vnode.context.$data, select.val(), binding.expression);
});
},
update: function (el, binding, newVnode, oldVnode) {
var select = $(el);
select.val(binding.value).trigger('change');
}
});
<select v-selected="userEditor.Id">
<option v-for="user in users" v-bind:value="user.id" >
{{ user.fullName}}
</option>
</select>
Description:
Suppose we have two $data's props: valOrObjectWithoutNesting and objLvl1:
data: function(){
return{
valOrObjectWithoutNesting: 'let it be some string',
objLvl1:{
objLvl2:{
objLvl3:{
objField: 'primitive string'
}
}
}
}
}
Variant with 1st level $data's-property:
<select v-selected="valOrObjectWithoutNesting">
// Now this code:
vnode.context.$data[binding.expression] = select.val();
// Equals to:
vnode.context.$data['valOrObjectWithoutNesting'] = select.val();
Variant with 4th level $data's-property:
<select v-selected="objLvl1.objLvl2.objLvl3.objField">
// Now this code:
vnode.context.$data[binding.expression] = select.val();
// Equals to:
vnode.context.$data['objLvl1.objLvl2.objLvl3.objField'] = select.val(); // error here
So the deepSet function in my code above "converting" $data['objLvl1.objLvl2.objLvl3.objField'] to $data['objLvl1']['objLvl2']['objLvl3']['objField'].
As you see, as I mentioned in comments to your question, when you want make select2-wrapper more customisable, the directive-way much more complicated, than separate component-way. In component, you would pass as much configuration props and event subscriptions as you want, you would avoid doing side mutations like vnode.context.$data[binding.expression] and your code would become more understandable and simpler for further support.
A custom directive is perfectly fine, except use the insertedhook instead of bind. Adapted from Vue Wrapper Component Example.
To bind to an object property, the simplest way is to wrap it in a computed setter Computed Setter and bind to that.
Note, 'deep setting' does not appear to work. The problem is one of change detection, which the computed setter overcomes. (Note that the on('change' function is jQuery not Vue.)
console.clear()
Vue.directive('selected', {
inserted: function (el, binding, vnode) {
var select = $(el);
select
.select2()
.val(binding.value)
.trigger('change')
.on('change', function () {
if (vnode.context[binding.expression]) {
vnode.context[binding.expression] = select.val();
}
})
},
});
var vm = new Vue({
el: '#my-app',
computed: {
selectedValue: {
get: function() { return this.myObj.type },
set: function (value) { this.myObj.type = value }
}
},
data: {
selectedVal: 0,
myObj: { type: 3 },
opts: [{
id: 1,
text: 'Test 1'
}, {
id: 2,
text: 'Test 2'
}, {
id: 3,
text: 'Test 3'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.4/vue.js"></script>
<div id="my-app">
<div>
<label for="example">Test dropdown list ({{ myObj.type }})</label>
</div>
<div>
<select id="example" style="width: 300px" v-selected="selectedValue">
<option v-for="(opt,index) in opts" :value="opt.id" :key="index">
{{ opt.text }}
</option>
</select>
</div>
</div>
Related
I'm developing a category selection template with pass in the previous selection.
But the select Only works on "on first loading" if I change the select so I get behavior error. (this unselect the parent)
I need a "light".
This is the link of project
https://zimeonline.com.br
I try changer the object vue data() in each ajax in my components
<template>
<div>
<select #change="category()" v-model="selectedId" v-bind:key="option[0].id" v-for="option in options" class="browser-default custom-select">
<option v-bind:value="op.id" v-bind:selected="op.selected==1" v-bind:key="op.id" v-for="op in option">{{op.name}}</option>
</select>
</div>
</template>
<script>
export default {
name: "ProductFormCategory",
data() {
return {
options: {},
selectedId:''
}
},
created() {
let vm = this;
vm.category();
},
methods: {
async category() {
let vm = this;
await vm.$http.get('category/'+vm.selectedId).then(function (response) {
vm.options = response.data;
}).catch(function () {
});
vm.$forceUpdate();
},
}
}
</script>
<style scoped>
.browser-default{
margin-bottom: 10px !important;
}
</style>
this URL list ALL FATHER categorys
https://api.zimeonline.com.br/api/category
this URL list ALL CHILDREN categorys
https://api.zimeonline.com.br/api/category/some_id(from father category)
exemple: https://api.zimeonline.com.br/api/category/5
Then 5 is ID from https://api.zimeonline.com.br/api/category
here an exemple of the atual code select
https://zimeonline.com.br (a litle slow in the fist time)
I'm not sure that I understand your questions. But I see some problems in your code:
Why did you put a v-for in both select and option?
In the first loop, you bind a key to option[0] instead of options[0] or option
Also a tip: You could use vm.$set to make reactive variables
Solution for category basead from parent id with select input option sub-menu
<script>
export default {
name: "ProductFormCategory",
props:{
setCategoryId:String
},
data() {
return {
options: {},
categoryId: ''
}
},
created() {
let vm = this;
vm.categoryId = vm.setCategoryId || ('');
vm.category();
},
methods: {
async category(event) {
let vm = this;
if (event) {
vm.categoryId = (event.target.value);
}
await vm.$http.get('category/' + vm.categoryId).then(function (response) {
vm.options = response.data;
}).catch(function () {
});
},
}
}
<template>
<div>
<select
#change="category($event)"
v-bind:key="option[0].id"
v-for="option in options"
class="browser-default custom-select">
<option value="">Selecione uma categoria</option>
<option
v-bind:value="op.id"
v-bind:selected="op.selected==1"
v-bind:key="op.id"
v-for="op in option">
{{op.name}}
</option>
</select>
</div>
I want to change the second select list according to the selected value in the first one. It worked when i did two Vue instances for each select, but i wanted to do a small app so its all a bit cleaner.
The types JSON array needs to be outside the Vue JS. You can see it in the fiddle.
Somehow i just dont get how to update the second selectlist.
Before i did something like this and it worked perfectly:
// methods of first select (category)
methods: {
update: function (value)
this.options = types[value]
}
}
...
// methods of second select (typselect)
methods: {
onChange(event) {
typselect.update(event.srcElement.value)
}
}
The app:
<div id="app">
<select v-model="category" v-on:change="onChange">
<option>Choose</option>
<option value="5">type1</option>
<option value="6">type2</option>
<option value="11">type3</option>
</select>
<select id="typselect">
<option v-for="option in options" v-bind:value="option.value">{{ option.text }}</option>
</select>
</div>
So i switched that for something like this:
new Vue({
el: '#app',
data: {
category: '5'
},
computed: {
options: function(event) {
console.log('should be called on change');
let options = ''
options = 1;
// options = types[event.srcElement.value]; // this would be so easy...
return options
}
},
methods: {
onChange: function(e) {
console.log(event.srcElement.value);
this.options = this.options
}
}
})
But i just don't get how to get the second selectlist updated.
Here come a fiddle:
https://jsfiddle.net/Honkoman/g9g5uukr/2/
Your computed should look like this.
computed: {
options: function(event) {
return types[this.category]
}
},
Updated fiddle.
I use select2 in my Angular project , Actually I have a problem that is I have no idea about how to set default value for select-option. Here is my code :
HTML :
<select-tag-manager parent-id="2" value="restaurant.type" ></select-tag-manager>
Angular :
app.directive('selectTagManager', function() {
return {
restrict: "E",
replace: true,
scope: {
parentId: '#',
value: '='
},
controller: function($rootScope, $scope, Gateway, toaster, $element, Tags) {
var element;
$scope.update = function () {
};
var makeStandardValue = function(value) {
var result = [];
angular.forEach(value , function(tag , key) {
if(result.indexOf(tag.tagId) < 0) {
result.push(tag.tagId);
}
});
return result;
};
var init = function () {
Gateway.get('', '/tag?' + 'parentId=' + $scope.parentId, function(response) {
$scope.allPossibleTags = response.data.result.tags;
});
element = $($element).children().find('select').select2();
console.log(element);
};
$scope.$watch('value', function(newval) {
if( newval ) {
$scope.standardValue = [];
angular.forEach(newval, function(val, key) {
$scope.standardValue.push(val.tagName);
});
console.log($scope.standardValue);
}
});
init();
},
templateUrl: 'selectTagManager.html'
}
});
selectTagManager.html:
<div class="row">
<div class="col-md-12">
{{ standardValue }}
<select class="select2" multiple="multiple" ng-model="standardValue" ng-change="update()">
<option ng-if="tag.tagId" ng-repeat="tag in allPossibleTags" data-id="{{tag.tagId}}" value="{{tag.tagId}}">{{ tag.tagName }}</option>
</select>
</div>
</div>
I got value
console.log($scope.standardValue);
result: ["lazzania", "pizza", "kebab"]
But I don't know how to set them as default value in select-option. Any suggestion?
EDITED :
I've just edited my question using Angular-ui/ui-select2. I changed my template :
<select ui-select2 = "{ allowClear : true }" ng-model="standardValue" multiple="multiple" >
<option value="standardId" ></option>
<option ng-repeat="tag in allPossibleTags" value="{{tag.tagId}}">{{tag.tagName}}</option>
</select>
And also my js:
$scope.$watch('value', function(newval) {
if( newval ) {
$scope.standardValue = [];
$scope.standardId = [];
// $scope.standardValue = makeStandardValue(newval);
console.log('----------------------------------------------------------------------');
angular.forEach(newval, function(val, key) {
$scope.standardValue.push(val.tagName);
$scope.standardId.push(val.tagId);
});
console.log($scope.standardValue);
console.log($scope.standardId);
}
});
Nevertheless , Still I can't set default value.
as demonstarted at http://select2.github.io/examples.html#programmatic, one can set default values for multiple select2 element as follows:
$exampleMulti.val(["CA", "AL"]).trigger("change");
so, in you case you have already element variable pointing to your select2:
element.val($scope.standardValue).trigger('change');
note, that this is jQuery approach of setting/changing values, angular approach would be to update values via ng model and its life cycle events
The IDs in your model need to match the IDs in your data source, so if your model is:
["lazzania", "pizza", "kebab"]
Then allPossibleTags needs to look like:
[{ tagId: "lazzania", tagName: "Lazzania" }, { tagId: "pizza" ...
Check out this plunk for a working example:
http://plnkr.co/edit/e4kJgrc69u6d3y2CbECp?p=preview
i'm really new to AngularJS and i like it very much.
But i'm experiencing a problem trying to initialize a prealoaded dropdown with a specific value.
The dropdown is initialized with values available from JSON array, but when i try to select a default value in this dropdown, i don't see that value selected but the ng-model variable is set correctly.
I created a plunker example here http://plnkr.co/edit/7su3Etr1JNYEz324CMy7?p=preview tryng to achieve what i want, but i can't get it to work. I tried with ng-repeat and ng-select, with no luck. Another try i did (in this example) is trying to set the ng-selected property.
This is a part of my html
<body ng-controller="MySampleController">
<select name="repeatSelect" id="repeatSelect" ng-model="SelectedStatus" ng-init="SelectedStatus">
<option ng-repeat="option in StatusList[0]" value="{{option.key}}" ng-selected="{{option.key==SelectedStatus}}">{{option.name}}</option>
</select>
<select name="repeatSelect" id="repeatSelect" ng-model="SelectedOrigin">
<option ng-repeat="option in OriginList[0]" value="{{option.key}}" ng-selected="{{option.key == SelectedOrigin}}">{{option.key}} - {{option.name}}</option>
</select>
<pre>Selected Value For Status: {{SelectedStatus}}</pre>
<pre>{{StatusList[0]}}</pre>
<pre>Selected Value For Origin: {{SelectedOrigin}}</pre>
<pre>{{OriginList[0]}}</pre>
</body>
And this is code from my controller
function MySampleController($scope) {
$scope.StatusList = [];
$scope.OriginList = [];
$scope.ServiceCall = {};
$scope.EntityList = [];
$scope.SelectedStatus = -3;
$scope.SelectedOrigin = 1;
var myList = [
{
item: 'Status',
values: [{ key: -3, name: 'Aperto' },
{ key: -1, name: 'Chiuso' }]
},
{
item: 'Origin',
values: [{ key: 1, name: 'Origin1' },
{ key: 2, name: 'Origin2' },
{ key: 3, name: 'Origin3' }]
}
];
$scope.documentsData = myList;
angular.forEach($scope.documentsData, function (value) {
$scope.EntityList.push(value);
switch ($scope.EntityList[0].item) {
case 'Status':
$scope.StatusList.push($scope.EntityList[0].values);
$scope.EntityList = [];
break;
case 'Origin':
$scope.OriginList.push($scope.EntityList[0].values);
$scope.EntityList = [];
break;
}
});
}
Any help would be appreciated!
Thanks in advance.
You can at least use ng-options instead of ng-repeat + option, in which case the default value works just fine.
<select name="repeatSelect" id="repeatSelect"
ng-options="opt.key as opt.key+'-'+opt.name for opt in StatusList[0]"
ng-model="SelectedStatus"></select>`
You can also make it a bit more readable by specifying the option label as a scope function.
HTML: ng-options="opt.key as getOptionLabel(opt) for opt in StatusList[0]"
Controller:
$scope.getOptionLabel = function(option) {
return option.key + " - " + option.name;
}
Plunker: http://plnkr.co/edit/7BcAuzX5JV7lCQh772oo?p=preview
Value of a select directive used without ngOptions is always a string.
Set as following and it would work
$scope.SelectedStatus = '-3';
$scope.SelectedOrigin = '1';
Read answer here in details ng-selected does not work with ng-repeat to set default value
I'm using a select box that is populated by the ng-options. Unfortunately, I cannot get my ng-change function to call.
Here is my Fiddle
Here is my js:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.typeOptions = [
{ name: 'Feature', value: 'feature' },
{ name: 'Bug', value: 'bug' },
{ name: 'Enhancement', value: 'enhancement' }
];
$scope.scopeMessage = "default text";
var changeCount = 0;
$scope.onSelectChange = function()
{
changeCount++;
this.message = "Change detected: " + changeCount;
}.bind($scope);
//$scope.form = {type : $scope.typeOptions[0].value};
$scope.form = $scope.typeOptions[0].value;
}
And here is my HTML:
<div ng-controller="MyCtrl" class="row">
<select ng-model='form' required ng-options='option.value as option.name for option in typeOptions' ng-change="onSelectChange"></select>
<div style="border:1px solid black; width: 100px; height:20px;">{{scopeMessage}}<div>
</div>
This is currently holding me up on my project for work, so any help will be geatly appreciated. Thanks!
2 things... both simple :)
ng-change=onSelectChange should be onSelectChange()
this.message should be this.scopeMessage
Your problem is that you are passing in the function reference to the ng-change directive. However, that directive expects an expression which can evaluated. So attach the parentheses to the function so that it can be evaluated as a function call.
Like here: http://jsfiddle.net/MTfRD/1101/
<select ng-model='form' required ng-options='option.value as option.name for option in typeOptions' ng-change="onSelectChange()"></select>