BookYourSeat: Automatic Seat Selection on Click using AngularJS [GIF]

11,355

Solution 1

I've changed your code with the following points to implement the logic:

  • changed model to 2d-Array style. seats = [ [Obj0, Obj1, Obj2], [Obj3, Obj4, Obj5] ]
  • calculated distance to border = how many seats to select for current row. Then we can calculate the rest of the available seats for current count.
  • added rang property to create the sections for different row groups.

I think I've implemented all your use cases.

Please have a look at the demo below or at this fiddle.

My code is pretty complicated as well but I think the loops are required because of the updating of the other selections.

angular.module('bookYourSeatApp', [])
	.factory('seats', SeatsFactory)
    .controller('mainCtrl', MainCtrl);

function SeatsFactory($rootScope, $timeout) {
	var seatProps = {
    	id: 0,
    	caption: 0,
        checked: false,
        booked: false
    };
	var seats = {
    	'firstRang': {
            //           col0 1 2 3 4  5 
        	// row 0 seat 0   1 2 3 4  5
            // row 1 seat 6   7 8 9 10 11
            seats: createSeats(2, 6) // rows, cols
        },
        'secondRang': {
        	seats: createSeats(3, 6)
        }
    };
    
    function createSeats(rows, cols) {
    	var arr = [[]];
        var seatIndex = 0;
        for (var row = 0; row < rows; row++) {
        	arr[row] = [];
            for(var col=0; col < cols; col++) {
            	var seat = angular.extend({}, seatProps, {
                	id: seatIndex,
                    caption: seatIndex,
                    booked: seatIndex < 5 // 0 to 5 booked
                });
            	arr[row][col] = seat;
                seatIndex++;
            }
        }
        return arr;
    }
    
	function checkSelected(newCount) {
    	// selected fewer or more than persons in select.
        // --> uncheck all
        var checkedCount=0, keys = Object.keys(seats);
        for (var rang=0; rang < keys.length; rang++) {
        	var key = keys[rang];
        	var curSeats = seats[key].seats;
            for (var row=0; row < curSeats.length; row++) {
                for (var col=0; col < curSeats[row].length; col++) {
                    if ( curSeats[row][col].checked ) {
                        checkedCount++;
                    }
                }
            }
            //console.log('new count', newCount, checkedCount);
            // we can have more or less selections after selection change
            // --> more inc availCount
            if (checkedCount === 0) {
            	// nothing selected
                factory.availCount = angular.copy(newCount);
            }
            else if (newCount.val > checkedCount) {
            	//console.log('add delta', newCount, checkedCount)
            	factory.availCount.val = (newCount.val - checkedCount);
            } else {
            	removeAllCheck();
            }
        }
    }
    
	function removeCheck(rang) {
    	// later pass user to this function (for now remove all checked)
        /*var curSeats = seats[rang].seats
        for (var row=0; row < curSeats.length; row++) {
            for (var col=0; col < curSeats[row].length; col++) {
            	curSeats[row][col].checked = false;
            }
        }*/
        keys = Object.keys(seats);
        
        for (var rang=0; rang < keys.length; rang++) {
        	var key = keys[rang];
        	var curSeats = seats[key].seats;
            for (var row=0; row < curSeats.length; row++) {
                for (var col=0; col < curSeats[row].length; col++) {
                    curSeats[row][col].checked = false;
                }
            }
        }
    }
    
    function removeAllCheck() {
     	keys = Object.keys(seats);
    	for (var rang=0; rang < keys.length; rang++) {
        	var key = keys[rang];
        	var curSeats = seats[key].seats;
            for (var row=0; row < curSeats.length; row++) {
                for (var col=0; col < curSeats[row].length; col++) {
                    curSeats[row][col].checked = false;
                }
            }
        }
    }
    
    function selectSeats(selection, count) {
    	// todo:
        // check distance to border, keep the rest as clickable
        // selection = {rang, row, seat}
		console.log(selection);
        var row = selection.row,
        	seat = selection.seat;
        
        if ( !seat.booked ) {
            //console.log('availCount', factory.availCount);
            if ( factory.availCount.val == 0 ) {
                //console.log('new selection');
                factory.availCount = angular.copy(count);
                removeCheck(); //selection.rang);
            }

            var borderDistance = row.length - row.indexOf(seat),
            	rest = borderDistance > count.val ? 0:  count.val - borderDistance;
                
			if ( factory.availCount.val === count.val) {
                // first click
                var lastIndex = rest > 0 ? row.length: row.indexOf(seat) + count.val;
                for ( var seatIndex = row.indexOf(seat); seatIndex < lastIndex; seatIndex++) {
                    row[seatIndex].checked = true;
                }
                factory.availCount.val = rest; // update available seats
            } 
            else {
                // second click dec. availCounter
                // single change of seats
                /*if ( factory.availCount.val < 0 ) {
                    row[row.indexOf(seat)].checked = false; // remove check
                    factory.availCount.val++;
                }
                else {*/
                if ( !row[row.indexOf(seat)].checked ) {
                	// only if not already checked
                    row[row.indexOf(seat)].checked = true;
                    if ( factory.availCount.val > 0 ) {
                        factory.availCount.val--;
                    }
                 }
                //}
            }
        }
    }
    
	var factory = {
    	map: seats,
        select: selectSeats,
        availCount: {},
        setAvailCount: function(count) {
        	console.log('avail', count);
            checkSelected(count);
        }
    };
    
    return factory
}

function MainCtrl(seats) {
	var vm = this;
    angular.extend(vm, {
    	seats: seats,
        selectionCount: [//[0,1,2,3,4],[
        {id: 0, val: 0}, // object for two-way binding
        {id: 1, val: 1},
        {id: 2, val: 2},
        {id: 3, val: 3},
        {id: 4, val: 4},
        ],
        selectedCount: 0
    });
    
    vm.selectedCount = vm.selectionCount[2];
    seats.setAvailCount(vm.selectedCount);
}
table {
    border: 1px solid black;
    padding: 0.5em;
}

td {
    padding: 1em;
    border: 2px solid gray;
}

td:hover {
    cursor: default;
    background-color: gray;
}

.active {
    border: 2px solid lightgreen;
    border-radius: 5px;
}

.booked {
    background-color: lightgray;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="bookYourSeatApp" ng-controller="mainCtrl as ctrl">
    <label>Persons <select ng-model="ctrl.selectedCount" ng-change="ctrl.seats.setAvailCount(ctrl.selectedCount)" ng-options="count as count.val for count in ctrl.selectionCount"></select></label>
    Seats left: {{ctrl.seats.availCount.val}}<br/>
    <table ng-repeat="(key, rang) in ctrl.seats.map">
        <tr ng-repeat="row in rang.seats">
            <td ng-repeat="seat in row" ng-class="{'active': seat.checked, 'booked': seat.booked}" ng-click="ctrl.seats.select({rang:key, row:row, seat: seat}, ctrl.selectedCount)">
                {{seat.caption}}
            </td>
        </tr>
    </table>
    <pre>{{ctrl.seats.map | json : 2}}</pre>
</div>

Solution 2

Your looping logic is a bit difficult to follow, but more critically, unnecessary. Instead of a click function that loops through the whole collection, have it just interact directly with the object bound to the element being clicked.

Change your anchor element binding to this: ng-click="clickSeat(item)", and use a function on the controller like this:

$scope.clickSeat = function(seat) {
    if (!seat.seat && !$scope.isDisabled) {
      if (seat.check) {
        seat.check = false;
        $scope.selectedSeatCount--;
      } else if ($scope.selectedSeatCount < $scope.selectedVal) {
        seat.check = true;
        $scope.selectedSeatCount++;
      }
    }
}

Here's an updated fiddle: https://jsfiddle.net/5vqxgtq3/12/

I don't know if this captures all the functionality you're looking for, but hopefully effectively demonstrates how the logic is easier to reason when it's no longer relying on a loop, and you can expand upon it from there.

Share:
11,355
rittam
Author by

rittam

Updated on June 04, 2022

Comments

  • rittam
    rittam almost 2 years

    BookyourSeat:

    It is an angularJs app that helps you book your seats for a movie show similar to bookmyshow .

    Seat Selection GIF]

    What the User Can Do (User Cases)?

    1. Select and Deselect the Seats with respect to the selectedVal, i.e if the selectedVal = 4 then the user can select only 4 seats in total.

    2. if the SelectedVal is less than 1 then the user should not be able to select the seat anymore unless the user deselect any of the previously selected seats and select again.

    3. Booked Seats Case: If the check value of a seat is true, then the user should not be able to select or deselect that seat(a.blocked CSS rule is Added for that purpose) since it is already selected by another user(Lets assume).

    Automatic Seat Selection Cases

    As shown in the GIF

    1. If the user selects 3 seats and click on the first seat in the first row it should automatically select 2 and 3 on the same row.

    First Case select Consecutive result

    1. If the user Selects 3 seats and clicks on the second last seat in the row then last two seats should be filled and the remaining seat should be filled where ever the user clicks.

    Second Case remaining seat selection case

    1. If the user selects 3 seats and clicks on only the last seat then only that seat should be filled.

    Last seat selection in the row

    In case of 4 seats.

    4 Seats selected but in different rows

    Problem:

    I am able to achieve the automatic selection process using angular.forEach() but cannot all the logics correctly.

    $scope.execute = function(i, j, itemVal, itemLetter) {
            angular.forEach($scope.obj, function(v, k) {
              if (v[i].val == itemVal && v[i].letter == itemLetter) {
                if (v[i].seat == true || ($scope.isDisabled && v[i].check == false)) {
                  return;
                }
                v[i].check = !v[i].check;
                if (v[i].check)
                  $scope.selectedVal -= 1;
                else
                  $scope.selectedVal += 1;
    
               //seatSelection
                var m = i;
                for (var l = 0; l < $scope.selectedVal; l++)
                  v[m++].check = true;
                    //seatSelectionEnd
    
                console.log(itemVal + " " + itemLetter);
                if ($scope.selectedVal < 1) {
                  $scope.isDisabled = true;
                } else {
                  $scope.isDisabled = false;
                }
              }
            });
          };
        }])
    

    Working Fiddle: https://jsfiddle.net/rittamdebnath/5vqxgtq3/11/