Angular with multiple selects that must have unique options selected - javascript

I have an angular page that will have a dynamic number of select elements on it. Each select will have the same option collection but once an option is selected from one, that option should be removed from all of the subsequent select elements.
I found this: http://jsfiddle.net/Zv5NE/63/ which works exactly how I'd like (when an option is selected from one select, it's removed from the others and then if that same select is changed, it adds the previously selected option back to the others).
The problem is, this is using a hard coded number of select elements and also using hard coded filters for each select element...that won't work for my purposes because, as I said, my users are going to need to be able to dynamically add n number of select elements.
I've done some playing around trying to create my own filter to accommodate for this, but I'm super green to angular (angular 1 btw) and I've hit the wall.
this is a small snippet from what I've tried. Essentially I've just tried creating an array and adding selected items to that array then checking against the values in the array for the filter (I would have to add some logic for changing options obviously, but I'm really not sure this is the right direction to go):
$scope.filter = function (item) {
for (i = 0; i < $scope.names.length; i++) {
if (item == $scope.names[i]) {
return false;
}
}
return true;
};
any guidance would be greatly appreciated.

I shelved this for a while but came back to it this morning. I was able to come up with a working solution.
Here's what I wrote up. May not be the most elegant way to do it, but it works for my purposes:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta name="viewport" content="width=device-width" />
<title>AngularTest</title>
</head>
<body ng-controller="HellowWorldCtrl">
<select ng-model="selectname0" ng-options="item as item.name for item in classes | customFilter:'selectname0':this">
<option value="">- select -</option>
</select>
<div id="selectsDiv"></div>
<br />
<input type="button" value="Add Select" ng-click="addSelect()" ng-show="cnt < classes.length -1" />
<script src="~/Scripts/angular.js"></script>
<script type="text/javascript">
var app = angular.module('app', []).controller('HellowWorldCtrl', function ($scope, $compile) {
$scope.cnt = 0;
$scope.selectsAdded = [];
$scope.selectsAdded.push('selectname0');
$scope.addSelect = function () {
$scope.cnt++;
$scope.selectsAdded.push('selectname' + $scope.cnt);
var newSelect = $compile('<div><select ng-model="selectname' + $scope.cnt + '" ng-options="item as item.name for item in classes | customFilter:\'selectname' + $scope.cnt + '\':this"><option value="">- select -</option></select></div>')($scope);
angular.element(document.getElementById('selectsDiv')).append(newSelect);
};
$scope.classes = [
{
id: 1,
name: 'Biology 101',
courseid: '12345'
},
{
id: 2,
name: 'Chemistry 101',
courseid: '12374'
},
{
id: 3,
name: 'Psychology 101',
courseid: '32165'
},
{
id: 4,
name: 'Geology 101',
courseid: '78945'
},
{
id: 5,
name: 'Math 101',
courseid: '65478'
}
];
});
app.filter('customFilter', function () {
return function (items, which, scope) {
var alreadySelectedCourses = [];
var courses = [];
for (i = 0; i < items.length; i++) { // loop over all of the items in the class array...cwc
for (j = 0; j < scope.selectsAdded.length; j++) { // loop over all of the selects added to the page...cwc
if (which == scope.selectsAdded[j]) { // check if the calling select is the same one in the loop...cwc
if (scope['selectname' + j] && scope['selectname' + j].id) { // check if the calling select has alraedy been selected...cwc
if (scope['selectname' + j].id == items[i].id) { // check if the selected value of the calling select is the same as the item in the iteration and add it to the return array if so...cwc
courses.push(items[i]);
alreadySelectedCourses.push(items[i]);
}
}
} else { // not the calling select so find out the value and don't add it to the return array...cwc
if ((scope['selectname' + j] && scope['selectname' + j].id)) { // other selects (not calling select) have values selected so add them to the alreadyselectedarray...cwc
if (scope['selectname' + j].id == items[i].id) {
alreadySelectedCourses.push(items[i]);
}
}
}
}
if (alreadySelectedCourses.indexOf(items[i]) > -1) {
continue;
} else {
courses.push(items[i]);
}
}
return courses;
}
});
</script>
</body>
</html>

Related

Tail Select using Check box

I want to perform multiple tail select using checkbox than a dropdown , i checked a code ,but i was not able to convert it to checkbox , Can anyone help me .
var projects = tail.select("select.projects", {
deselect: true,
multiContainer: ".projects-selected",
multiple: true,
});
projects.config("multiple", false, true);
projects.on('change', changeFirst);
let tests = tail.select("select.tests", {
multiContainer: ".tests-selected",
disabled: true
});
function getData() {
var size = Math.floor(Math.random() * 6) + 5;
var randomEntry = function() {
var tmp = CryptoJS.MD5(Math.floor(Math.random() * 1000).toString()).toString();
console.log(tmp);
return {
id: 'ID_' + tmp,
name: tmp
};
};
return Array.from({
length: size
}, randomEntry);
}
function changeFirst(opt, event) {
if (event == 'select') {
let data = getData();
let items = {};
data.forEach(function(entry) {
items[entry.id] = entry.name
});
tests.config('items', items);
tests.config('disabled', false);
} else if (event == 'unselect') {
tests.config('disabled', true);
tests.config('items', {});
}
}
<div class="left">
<select class="projects">
<option>Project 1</option>
<option>Project 2</option>
</select>
</div>
<div class="right">
<span class="projects-selected"></span>
<span class="tests-selected"></span>
</div>
i tried editing this code , but am not getting what i wanted , so can anyone help me to sort it out .
Output should be something like this :
so when we click on the span generated it should unselect . , but whatever try to provide checkbox is not working
NOTE :
tail. select is a rewritten version of the jQuery tail. select plugin that can be used to beautify & enhance the default select box with no dependency
https://www.cssscript.com/single-multiple-select-tail/#:~:text=Description%3A,select%20box%20with%20no%20dependency.
I don't know whether this can be done using tail.select , If not possible , can anyone suggest any other way to do this .

Issue with <select ng-options="..." />

I tried to achieve a simple feature with AngularJS as below. In item list, when user clicks an item and click the Remove button then the item will be removed.
html:
<div ng-app="app" ng-controller="MainController">
<div>
<select ng-options="item.name for item in items" ng-model="currentItem" size="5" style="width: 200px"></select>
</div>
<button ng-click="removeItem()">Remove</button>
</div>
and script is like below:
angular.module('app', [])
.controller('MainController', function($scope) {
$scope.items = [{
name: 'item1'
}, {
name: 'item2'
}, {
name: 'item3'
}];
$scope.currentItem = $scope.items[0];
$scope.removeItem = function() {
var index = $scope.items.indexOf($scope.currentItem);
$scope.items.splice(index, 1);
};
});
The problem is when I tried to remove an item (i.e. item2), the list always shows an empty item in the first position. When I click 'item1' or 'item3', the empty item disappears.
I know that this is caused by ng-model="currentItem" in html. The item that currentItem points to get removed, currentItem points to null. So I changed the function removeItem as below to solve this issue.
$scope.removeItem = function() {
var index = $scope.items.indexOf($scope.currentItem);
$scope.items.splice(index, 1);
/* PART 1 begin */
if ($scope.items.length === 0) {
$scope.currentItem = null;
} else {
if (index >= 0 && index <= $scope.items.length - 1) {
$scope.currentItem = $scope.items[index];
} else {
$scope.currentItem = $scope.items[$scope.items.length - 1];
}
}
/* PART 1 end */
};
I would like to know whether there is any simple way (like a directive) in AngularJS to do the action in PART 1 automatically.
There is simple way in which you can prevent that is just include
<option value="" ng-show="false"></option>
in select like as shown below
<select ng-options="item as item.name for item in items" ng-model="currentItem" size="5" style="width: 200px">
<option value="" ng-show="false"></option>
</select>
Working Demo
UPDATE 1
I have resolved the issue of not highlighting the last item, Take a look the working demo
$scope.removeItem = function () {
var index = $scope.items.indexOf($scope.currentItem);
$scope.items.splice(index, 1);
index === $scope.items.length ? $scope.currentItem = $scope.items[index - 1] : $scope.currentItem = $scope.items[index];
};
Working Demo

Fast filter in a list of records with JavaScript

I have a list with about 10 000 customers on a web page and need to be able to search within this list for matching input. It works with some delay and I'm looking for the ways how to improve performance. Here is simplified example of HTML and JavaScript I use:
<input id="filter" type="text" />
<input id="search" type="button" value="Search" />
<div id="customers">
<div class='customer-wrapper'>
<div class='customer-info'>
...
</div>
</div>
...
</div>
<script type="text/javascript">
$(document).ready(function() {
$("#search").on("click", function() {
var filter = $("#filter").val().trim().toLowerCase();
FilterCustomers(filter);
});
});
function FilterCustomers(filter) {
if (filter == "") {
$(".customer-wrapper").show();
return;
}
$(".customer-info").each(function() {
if ($(this).html().toLowerCase().indexOf(filter) >= 0) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
}
</script>
The problem is that when I click on Search button, there is a quite long delay until I get list with matched results. Are there some better ways to filter list?
1) DOM manipulation is usually slow, especially when you're appending new elements. Put all your html into a variable and append it, that results in one DOM operation and is much faster than do it for each element
function LoadCustomers() {
var count = 10000;
var customerHtml = "";
for (var i = 0; i < count; i++) {
var name = GetRandomName() + " " + GetRandomName();
customerHtml += "<div class='customer-info'>" + name + "</div>";
}
$("#customers").append(customerHtml);
}
2) jQuery.each() is slow, use for loop instead
function FilterCustomers(filter) {
var customers = $('.customer-info').get();
var length = customers.length;
var customer = null;
var i = 0;
var applyFilter = false;
if (filter.length > 0) {
applyFilter = true;
}
for (i; i < length; i++) {
customer = customers[i];
if (applyFilter && customer.innerHTML.toLowerCase().indexOf(filter) < 0) {
$(customer).addClass('hidden');
} else {
$(customer).removeClass('hidden');
}
}
}
Example: http://jsfiddle.net/29ubpjgk/
Thanks to all your answers and comments, I've come at least to solution with satisfied results of performance. I've cleaned up redundant wrappers and made grouped showing/hiding of elements in a list instead of doing separately for each element. Here is how filtering looks now:
function FilterCustomers(filter) {
if (filter == "") {
$(".customer-info").show();
} else {
$(".customer-info").hide();
$(".customer-info").removeClass("visible");
$(".customer-info").each(function() {
if ($(this).html().toLowerCase().indexOf(filter) >= 0) {
$(this).addClass("visible");
}
});
$(".customer-info.visible").show();
}
}
And an test example http://jsfiddle.net/vtds899r/
The problem is that you are iterating the records, and having 10000 it can be very slow, so my suggestion is to change slightly the structure, so you won't have to iterate:
Define all the css features of the list on customer-wrapper
class and make it the parent div of all the list elements.
When your ajax request add an element, create a variable containing the name replacing spaces for underscores, let's call it underscore_name.
Add the name to the list as:
var customerHtml = "<div id='"+underscore_name+'>" + name + "</div>";
Each element of the list will have an unique id that will be "almost" the same as the name, and all the elements of the list will be on the same level under customer-wrapper class.
For the search you can take the user input replace spaces for underscores and put in in a variable, for example searchable_id, and using Jquery:
$('#'+searchable_id).siblings().hide();
siblings will hide the other elements on the same level as searchable_id.
The only problem that it could have is if there is a case of two or more repeated names, because it will try to create two or more divs with the same id.
You can check a simple implementation on http://jsfiddle.net/mqpsppxm/
​

Filtering array on multiple options to return options in new select

I am supporting some legacy code and having some issues in implementing a new feature.
I have 2 multiple select options, first one showing a list of states, second showing a list of cities. The idea is that the list of cities will be filtered dependant on the selected states in the first select.
Currently the cities are contained in a javascript array which is used in an if statement to attach the relevant options to the select, howeber this only works on a single state selection.
How can I filter the cities on multiple state selections?
I have tried numerous if statement combinations with no luck. My jquery knowledge is obviously lacking and haven't found anything similar in google/stackoverflow - however I appreciate I may be approaching this from the wrong angle.
<form name="newform">
<select multiple size="10" name="getStateSelect" id="getStateSelectID" onchange="GetSeries(document.newform.getStateSelect.options [document.newform.getStateSelect.selectedIndex].value);">
<option value="1">Alaska</option>
<option value="2">Arizona</option>
<option value="3">Californa</option>
</select>
<br />
<br />
<select id="getCitySelect" multiple="multiple" size="10"></select>
</form>
var fsArray = [{id: 1,stateid: 1,cityname: "Anchorage"},
{id: 2,stateid: 1,cityname: "Fairbanks"},
{id: 3,stateid: 1,cityname: "Wasilla"},
{id: 4,stateid: 2,cityname: "Flagstaff"},
{id: 5,stateid: 2,cityname: "Phoenix"},
{id: 6,stateid: 2,cityname: "Tucson"},
{id: 7,stateid: 3,cityname: "Fremont"},
{id: 8,stateid: 3,cityname: "Lakeport"},
{id: 9,stateid: 3,cityname: "Los Angeles"}];
function GetSeries(i) {
var SeriesSelectBox = document.getElementById("getCitySelect");
SeriesSelectBox.options.length = 0;
for (j in fsArray) {
if (fsArray[j].stateid == i) {
var seriesLength = SeriesSelectBox.options.length;
SeriesSelectBox.options[seriesLength] = new Option(fsArray[j].cityname, fsArray[j].id);
}
}
}
http://jsfiddle.net/fcp3h7mw/1/
Any help greatly appreciated!
Thanks
I just added a for loop on the getStateSelectID values to check if each element for selection status, if selected it runs your loop to add the cities.
the fiddle: http://jsfiddle.net/fcp3h7mw/5/
javascript:
var fsArray = [
{id:1,stateid:1,cityname:"Anchorage"},
{id:2,stateid:1,cityname:"Fairbanks"},
{id:3,stateid:1,cityname:"Wasilla"},
{id:4,stateid:2,cityname:"Flagstaff"},
{id:5,stateid:2,cityname:"Phoenix"},
{id:6,stateid:2,cityname:"Tucson"},
{id:7,stateid:3,cityname:"Fremont"},
{id:8,stateid:3,cityname:"Lakeport"},
{id:9,stateid:3,cityname:"Los Angeles"}
];
function GetSeries(i) {
var id = document.getElementById("getStateSelectID");
var SeriesSelectBox = document.getElementById("getCitySelect");
SeriesSelectBox.options.length = 0;
var states = 0;
for (states=0; states < id.length; states++) {
if(id[states].selected){
for (j in fsArray) {
if (fsArray[j].stateid == id[states].value) {
var seriesLength = SeriesSelectBox.options.length;
SeriesSelectBox.options[seriesLength] = new Option(fsArray[j].cityname, fsArray[j].id);
}
}
}
}
}
Markup: I also took out a lot of your function call in the markup
<select multiple size="10" name="getStateSelect" id="getStateSelectID" onchange="GetSeries();">
I've updated your code to be able to select multiple states and display the correct cities.
Essentially, you needed to get a list of options with attribute "selected" and store the option values in an array (onchange)
Afterwards, you would iterate through the new array and match against the values in the array instead of a single value.
Here's the updated code: http://jsfiddle.net/biz79/fcp3h7mw/3/
var fsArray = [
{id:1,stateid:1,cityname:"Anchorage"},
{id:2,stateid:1,cityname:"Fairbanks"},
{id:3,stateid:1,cityname:"Wasilla"},
{id:4,stateid:2,cityname:"Flagstaff"},
{id:5,stateid:2,cityname:"Phoenix"},
{id:6,stateid:2,cityname:"Tucson"},
{id:7,stateid:3,cityname:"Fremont"},
{id:8,stateid:3,cityname:"Lakeport"},
{id:9,stateid:3,cityname:"Los Angeles"}
];
function getSeries() {
var SeriesSelectBox = document.getElementById("getCitySelect");
SeriesSelectBox.options.length = 0;
var arr = getStateSelected();
for (var i = 0; i< arr.length; i++) {
for (var j in fsArray) {
if (fsArray[j].stateid == arr[i]) {
var seriesLength = SeriesSelectBox.options.length;
SeriesSelectBox.options[seriesLength] = new Option(fsArray[j].cityname, fsArray[j].id);
}
}
}
}
function getStateSelected() {
var sel = document.querySelectorAll('#getStateSelectID option');
var arr = [];
for (var i = 0; i< sel.length; i++ ) {
if ( sel[i].selected ) {
arr.push(sel[i].value);
}
}
return arr;
}
Hope this helps. Let me know if you have questions.

Moving objects between two arrays

I have two lists formed from an array containing objects.
I'm trying to move objects from one list to the other and vice versa.
Controller:
spApp.controller('userCtrl',
function userCtrl($scope,userService,groupService){
//Generate list of all users on the SiteCollection
$scope.users = userService.getUsers();
//array of objects selected through the dom
$scope.selectedAvailableGroups;
$scope.selectedAssignedGroups;
//array of objects the user actually belongs to
$scope.availableGroups;
$scope.assignedGroups;
//Generate all groups on the site
$scope.groups = groupService.getGroups();
//Boolean used to disable add/remove buttons
$scope.selectedUser = false;
//Take the selectedAvailableGroups, add user to those groups
//so push objects to "assignedGroups" array and remove from "avaiableGroups" array
$scope.addUserToGroup = function (){
userService.addUserToGroup($scope.selectedUser, $scope.selectedAvailableGroups, $scope.assignedGroups, $scope.availableGroups)
};
}
);
Service:
spApp.factory('userService', function(){
var addUserToGroup = function (selectedUser, selectedAvailableGroups, assignedGroups, availableGroups) {
var addPromise = [];
var selectLength = selectedAvailableGroups.length;
//Add user to selected groups on server
for (var i = 0; i < selectLength; i++) {
addPromise[i] = $().SPServices({
operation: "AddUserToGroup",
groupName: selectedAvailableGroups[i].name,
userLoginName: selectedUser.domain
});
};
//when all users added, update dom
$.when.apply($,addPromise).done(function (){
for (var i = 0; i < selectLength; i++) {
assignedGroups.push(selectedAvailableGroups[i]);
availableGroups.pop(selectedAvailableGroups[i]);
};
//alert(selectedUser.name + " added to: " + JSON.stringify(selectedAvailableGroups));
});
}
}
Object:
[{
id: 85,
name: Dev,
Description:,
owner: 70,
OwnerIsUser: True
}]
HTML:
<div>
<label for="entityAvailable">Available Groups</label>
<select id="entityAvailable" multiple
ng-model="selectedAvailableGroups"
ng-options="g.name for g in availableGroups | orderBy:'name'">
</select>
</div>
<div id="moveButtons" >
<button type="button" ng-disabled="!selectedUser" ng-click="addUserToGroup()">Add User</button>
<button type="button" ng-disabled="!selectedUser" ng-click="removeUserFromGroup()">Remove</button>
</div>
<div>
<label for="entityAssigned">Assigned Groups</label>
<select id="entityAssigned" multiple
ng-model="selectedAssignedGroups"
ng-options="g.name for g in assignedGroups | orderBy:'name'">
</select>
</div>
Right now, the push into assigned groups works but only updates when I click on something else or in the list, not really dynamically. But the biggest issue is the .pop() which I don't think works as intended.
$.when.apply($,addPromise).done() seems not to be angular api or synchronous. So angular is not aware of your changes. You must wrap your code inside a $scope.$apply call:
$scope.$apply(function(){
for (var i = 0; i < selectLength; i++) {
assignedGroups.push(selectedAvailableGroups[i]);
availableGroups.pop(selectedAvailableGroups[i]);
};
});
If you click on something, a $digest loop will happen and you will see your changes.
Your pop did not work because Array.pop only removes the last element. I guess that is not what you want. If you want to remove a specific element you should use Array.splice(),

Categories

Resources