Filtering a dropdown in Angular - html

I have a requirement for a select html element that can be duplicated multiple times on a page. The options for these select elements all come from a master list. All of the select elements can only show all of the items in the master list that have not been selected in any of the other select elements unless they just were duplicated.
When you select a new item from a duplicated select element, it seems to select the option after the one you selected even though the model still has the correct one set. This always seems to happen in IE11 and it happens sometimes in Chrome.
I realize this sounds convoluted, so I created a jFiddle example.
Try these steps:
Select Bender
Click the duplicate link
Select Fry (on the duplicated select)
Notice that the one that is selected is Leela but the model still has Fry (id:2) as the one selected
Can anyone tell me how I might get around this or what I might be doing wrong?
Here is the relevant Angular code:
myapp.controller('Ctrl', function ($scope) {
$scope.selectedIds = [{}];
$scope.allIds = [{ name: 'Bender', value: 1},
{name: 'Fry', value: 2},
{name: 'Leela', value: 3 }];
$scope.dupDropDown = function(currentDD) {
var newDD = angular.copy(currentDD);
$scope.selectedIds.push(newDD);
}
});
angular.module('appFilters',[]).filter('ddlFilter', function () {
return function (allIds, currentItem, selectedIds) {
//console.log(currentItem);
var listToReturn = allIds.filter(function (anIdFromMasterList) {
if (currentItem.id == anIdFromMasterList.value)
return true;
var areThereAny = selectedIds.some(function (aSelectedId) {
return aSelectedId.id == anIdFromMasterList.value;
});
return !areThereAny;
});
return listToReturn;
}
});
And here is the relevant HTML
<div ng-repeat="aSelection in selectedIds ">
Duplicate
<select ng-model="aSelection.id" ng-options="a.value as a.name for a in allIds | ddlFilter:aSelection:selectedIds">
<option value="">--Select--</option>
</select>
</div>

Hi I have just made a small change in your dupDropDown function as follows
$scope.dupDropDown = function(currentDD) {
$scope.selectedIds.push({});
}
Please check if this works for you.

Related

VueJs - Updating class with a setInterval function not working [duplicate]

I'm new to Vuejs. Made something, but I don't know it's the simple / right way.
what I want
I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:
this.items[index] = val;
this.items.push();
I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?
Simple code is here:
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items[index] = val;
this.items.push();
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button>
{{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
EDIT 2
For all object changes that need reactivity use Vue.set(object, prop, value)
For array mutations, you can look at the currently supported list here
EDIT 1
For vuex you will want to do Vue.set(state.object, key, value)
Original
So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).
Splice is still available and here is a link to array mutation methods available in vue link.
VueJS can't pickup your changes to the state if you manipulate arrays like this.
As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items.$set(index, val)
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button> {{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
As stated before - VueJS simply can't track those operations(array elements assignment).
All operations that are tracked by VueJS with array are here.
But I'll copy them once again:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
During development, you face a problem - how to live with that :).
push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs.
So I can share some of the approaches, that are used the most working with arrays.
You need to replace Item in Array:
// note - findIndex might be replaced with some(), filter(), forEach()
// or any other function/approach if you need
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
return (replacementItem.id === item.id)
})
this.values.splice(index, 1, replacementItem)
Note: if you just need to modify an item field - you can do it just by:
this.values[index].itemField = newItemFieldValue
And this would be tracked by VueJS as the item(Object) fields would be tracked.
You need to empty the array:
this.values.splice(0, this.values.length)
Actually you can do much more with this function splice() - w3schools link
You can add multiple records, delete multiple records, etc.
Vue.set() and Vue.delete()
Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):
Vue.set(plan, 'editEnabled', true) //(or this.$set)
And send edited data back to the back-end in the same format doing this before the Axios call:
Vue.delete(plan, 'editEnabled') //(or this.$delete)
One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.
So you this should work as well:
var tempArray[];
tempArray = this.items;
tempArray[targetPosition] = value;
this.items = tempArray;
This then should also update your DOM.
Observe object and array reactivity here:
https://v2.vuejs.org/v2/guide/reactivity.html

Selected Options doesnt display on the field but is sent correctly to backend

So i have a project in which i have to display some data from the table. Now i want to change the size of the data based on a field above the table that is actually a select input field and sends a value to the angular controller. Now this is working perfectly except for the fact that the field doesn't show the selected number of data being displayed on the field.
This is the empty field. but the data is inserted correctly. Also on debugging I found another option here on the field that is not in the html code. Here's my code for the html and the controller.
View:
<li class="manual-dropdown pull-right">
<select id="ddPageSize" ng-model="PaginationInfo.pageSizeSelected" ng-change="ChangePageSize()" aria-controls="DepartmentTable" class="form-control pull-right">
<option value="5">5</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="-1">All</option>
</select>
</li>
Controller:
$scope.PaginationInfo = {
maxSize: 5, // Limit number for pagination display number.
totalCount: 0, // Total number of items in all pages. initialize as a zero
pageIndex: 1, // Current page number. First page is 1.
pageSizeSelected: 5, // Maximum number of items per page.
}
GetData(searched);
function GetData(searched) {
//debugger
//var noOfPages = 1;
var SearchData = $scope.StatusSearch.Search;
if (SearchData == "") {
searched = false;
}
var Displaysize = $scope.PaginationInfo.pageSizeSelected;
var index = $scope.PaginationInfo.pageIndex;
if (searched == false) {
Get("/User/GetData?Size=" + Displaysize + "&index=" + index, false).then(function (d) {
//$("#").val()
//$scope.userAccount.CountryID = $("#ddCountryOptions").val();
// $scope.PaginationInfo.maxSize = d.info.maxSize;
$scope.PaginationInfo.totalCount = d.totalSize;
$scope.PaginationInfo.pageIndex = d.index;
$scope.PaginationInfo.pageSizeSelected = d.size;
//$scope.noOfPages = $scope.PaginationInfo.totalCount / $scope.PaginationInfo.pageSizeSelected;
$scope.accountlist = d.GetList;
$scope.$apply();
})
}
else {
// alert($scope.SearchData.Search);
Get("/User/SearchData?inputstring="+ SearchData, false).then(function (d) {
$scope.accountlist = d.GetList;
$scope.PaginationInfo.pageIndex = index;
$scope.PaginationInfo.pageSizeSelected = Displaysize;
$scope.PaginationInfo.totalCount = d.totalSize;
$scope.$apply();
});
}
}
explanation for the Controller: The data is loaded on page load so the GetData() function is called immediately. the default page size is set to 5 as shown and when i make a change to the field i recall the GetData() function with page size as a argument and the back end does the rest and returns a amount of data that i asked for. Also the reason there are 2 ajax calls in this function is to implement a search function. which check if the input field is empty or has a value and based on that output the data.
What i want to know is why is the page size field on my dropdown empty when i select a value.
Edit:
After a bit more research i found that the ng-Model is making a empty option with the value of the option i selected. Now the problem still remains i don't know how to display the value in the empty object. if i do select another option as selected, my ng-model value does not change. So i am still stuck with this. Also i have already give the ng-model an default value of 5 the same as my first dropdown option. so in case i tag any other option as selected, the ng-model option will remain 5 no matter how many times i change the dropdown value.
Alright i kind of solved my issue, though I am not sure if this is a good way to do it.
So what i did is simply bind the pageSizeSelected Value to the html select element by id.
$("#ddPageSize").val(d.size)
$scope.pageSizeSelected = $("#ddPageSize").val();
before $scope.$apply and it worked. Now when i select a value from the field it changes and displays the value i selected.

How to add legend for a bar chart with different colors in dc.js?

Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.

AngularJS next/prev word

I want to show to previous and next word,
and when the current one is 'fuga' one please let the next one will be first one('hoge'), same thing when the current one is first one the prev should be last one from list - 'fuga'
could you help me? please
[http://jsfiddle.net/zeck/L8Snx/][1]
This is probably what you're looking for. Working fiddle: http://jsfiddle.net/L8Snx/37/
HTML
<div>
<strong>Previous:</strong> {{getPrev().name}}
<strong>Current:</strong> {{current.name}}
<strong>Next:</strong> {{getNext().name}}
</div>
JavaScript
$scope.getNext = function() {
var i = $scope.getIndex($scope.current.index, 1);
return $scope.dataSet[i];
};
$scope.getPrev = function() {
var i = $scope.getIndex($scope.current.index, -1);
return $scope.dataSet[i];
};

Nested options using angular

$scope.senders = {}
$scope.senders.list = [{name: NYC, supportedSendingMethods: ['Send by mail', 'Send by sms', 'c']}, {name: GEN, supportedSendingMethods: ['Send by mail','Send by sms','c']}];
$scope.senders.selected = $scope.senders[0];
$scope.senders.selectedSending = $scope.senders[0].supportedSendingMethods[0];
First select:
<select ng-model="senders.selected" ng-options="sender.name for sender in senders">
</select>
//This one works as expected
Supported sending method:
<select ng-model="senders.selectedSending" ng-options="supp.supportedSendingMethods for supp in senders | filter:{name:senders.selected.name} ">
</select>
The last select shows all supported sending methods for the selected sender. The problem is that the options in the second select are arraylists themselves.
How can I go one level deeper (with filters) and show
1) show the supportedSendingMethods
2) of the selected sender?
As you already have selected sender stored into senders.selected variable then why just not use it? This is the best option I think:
ng-options="supp for supp in senders.selected.supportedSendingMethods"
Just to answer the original question I may propose two ways achieve what you need with filtering expression. One way is just access first item of the filtered collection - angular.js supports such expressions:
ng-options="supp for supp in (senders | filter:{name:senders.selected.name})[0].supportedSendingMethods "
Another option is to define separate filter for that - it will look better a bit:
ng-options="supp for supp in senders | filter:{name:senders.selected.name} | getFirst:'supportedSendingMethods'"
You may see all ways in actions here - http://jsfiddle.net/GRaAL/WMAea/.
You can try this way without filters. Th point is to set model of senders.currentSender as list for second select:
HTML
<div ng-controller="fessCntrl">
<select ng-model="senders.currentSender"
ng-options="sender.name for sender in senders.list"></select>
<select ng-model="supp"
ng-options="supp for supp in senders.currentSender.supportedSendingMethods"
ng-init="supp=senders.currentSender.supportedSendingMethods[0]"
></select>
</div>
JS
var fessmodule = angular.module('myModule', []);
fessmodule.controller('fessCntrl', function ($scope) {
$scope.senders = {};
$scope.senders.list = [{
name: 'NYC',
supportedSendingMethods: ['Send by mail1', 'Send by sms1', 'c1']
}, {
name: 'GEN',
supportedSendingMethods: ['Send by mail2', 'Send by sms2', 'c2']
}];
$scope.senders.currentSender = $scope.senders.list[0];
$scope.supp = $scope.senders.currentSender.supportedSendingMethods[0];
});
By adding the $watch we can now select by default 1st value:
$scope.$watch(function () {
return $scope.senders.currentSender;
},
function (newValue, oldValue) {
$scope.supp = newValue.supportedSendingMethods[0];
}, true);
Demo Fiddle