Implement Checkers in Salesforce with Visualforce and LWC


I wanted to explore a little bit the LWC with uncommon process for learning and also to know how complicate it will be to transform visualforce page with javascript/jqeury into Lightning.

So I decided to implement Checkers game. Was pretty confident about implementing with visualforce but had some doubts with LWC.


I used single custom object - Game that represent game between 2 users. Main flow is that first user create new game and wait for opponent to join the table, once the opponent join and both confirm the game starts. 

Obviously to show checkers board I needed to write custom code and might want to override the standard actions of the Game, so when user go to specific game record he will view the board (either as 1 of the players or as viewer).

At first I did it with visualforce and apex controller.

Main sections of the class is initialize the relevant structures - player 1/2 tools, board and its cells, movement log.

It have also 1 public function - moveTool that being called from the the page when player want to move tool. 

Rest of the functions are private to check valid movements, king options, etc... I didn't add here the full code as it too long. The full code can be viewed here https://github.com/liron50/SFCheckers.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public with sharing class PlayGameController{

    public String gameRecordId;
    public Game__c gameRecord {get; set;}
    public list<RowBoard> l_rows {get; set;}
    
    public Boolean isCurrentUser1 {get; set;}
    public Boolean isCurrentUser2 {get; set;}
    
    public map<String, PlayerTool> p1Tools {get; set;}
    public map<String, PlayerTool> p2Tools {get; set;}
    
    public map<String, CellBoard> allCellsMap {get; set;}
    public String allCellsJson {get; set;}

    public list<MovementItem> l_movementItems {get; set;}

    public PlayGameController(ApexPages.StandardController sc){
        gameRecordId = sc.getRecord().Id;
        
        initGameData();
    }
    
    private void initGameData(){
    
		//Init all the class variables
		...
    }
    
	//call from the page when player wants to move tool
    public PageReference moveTool(){
        
		...
    }
    
    //move player tool 1 move
    private void movePlayerTool(
        Integer playerNum, 
        String fromCell, 
        String toCell, 
        Integer toRow, 
        Integer toCol){
		...
    }
    
    //Check if there were any tools that had option to eat but did not eat
    private set<String> checkForBurnTools(Integer playerNum){
		...
    }
    
    //Check if player have any valid moves , if not the game end in draw
    private boolean anyValidMovement(Integer playerNum){
        ...
    }
    
    //Special logic to check if king tool had option to move
    private Boolean isKingCanMove(
        Integer playerNum, Integer opponent, Integer row, Integer col, String dirVert, String dirHorz){
		...
    }
    
    //Special logic to check if king tool had option to eat in specific direction
    private Boolean isKingCanEat(
        Integer playerNum, Integer opponent, Integer row, Integer col, String dirVert, String dirHorz){
		...
    }
}



Next is the visualforce page (again omitted here the JS/CSS code to save space). The main section of the page is simply HTML table that display the board cells and each cell content depend on the current user (is it player1, player2 or viewer) and on the tool in the cell (can be white/black checker/king or empty).


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<apex:page standardController="Game__c" extensions="PlayGameController" sidebar="false" lightningStylesheets="true">

<apex:includeScript value="/soap/ajax/34.0/connection.js"/>
<apex:includeScript value="/soap/ajax/34.0/apex.js"/>
<apex:includeScript value="{!URLFOR($Resource.jquery_1_11_1_min)}" />

<apex:outputPanel id="globalVars">
    <script type="text/javascript">
        
        var allCellsMap = JSON.parse('{!allCellsJson}');
        
        var namespace = '';
        var turn = '{!gameRecord.Player_Turn__c}';
        var isUser1 = '{!isCurrentUser1}';
        var isUser2 = '{!isCurrentUser2}';
        var gameId = '{!gameRecord.Id}';
		
        if((turn === 'P2' && isUser1 === 'true')
            || (turn === 'P1' && isUser2 === 'true')
            || (isUser1 === 'false' && isUser2 === 'false')) {
            console.log('STARTING TIMER');
            //start timer
            var interval = setInterval(function(){
                sforce.connection.sessionId = '{!$Api.Session_ID}'; 
                
                var expectingNextTurn = turn == 'P2' ? 'P1' : 'P2';
                var result = sforce.connection.query('select ' + namespace + 'Player_Turn__c from ' + namespace + 'Game__c where Id=\'' + gameId +  '\' and ' + namespace + 'Player_Turn__c = \'' + expectingNextTurn + '\'');
                var records = result.getArray("records");
            
                if(records[0] != undefined){
                    clearInterval(interval);
                    refreshBoardJS();
                }
            }, 3000);
        }
    </script>
</apex:outputPanel>


<style>
...
</style>

<script>
...
</script>


<apex:form id="formId">
<table>
    <tr>
        <td style="width:75%">
            <div class="boardstyle">
                
                    
                <div class="boardTitle">
                    {!gameRecord.Player_1__r.Name} VS {!gameRecord.Player_2__r.Name}
                </div>
                
                <div class="playerImg">
                    <img class="{!IF(gameRecord.Player_Turn__c == 'P2', 'playerImgBorder', 'playerWaitImgBorder')}" src="{!IF(isCurrentUser2, gameRecord.Player_1__r.SmallPhotoUrl, gameRecord.Player_2__r.SmallPhotoUrl)}" title="{!IF(isCurrentUser2, gameRecord.Player_1__r.Name, gameRecord.Player_2__r.Name)}"/>
                </div>
                <table>
                    <apex:repeat value="{!l_rows}" var="row">
                        <tr>
                            <apex:repeat value="{!row.l_cells}" var="cell">
                                <td class="{!IF(cell.isEven, 'evenbg', 'oddbg')}">
                                    <apex:outputPanel rendered="{!isCurrentUser1}">
                                        <div onclick="cellClicked('1', {!cell.rowNumber}, {!cell.colNumber}); return false;" class="player1Tool cl_a1_{!cell.cellIndexId}" style="{!IF(cell.status == 'P1' && gameRecord.Player_Turn__c == 'P1', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9813;
                                            </div>
                                        </div>
                                        <div class="player1Tool cl_d1_{!cell.cellIndexId}" style="{!IF(cell.status == 'P1' && gameRecord.Player_Turn__c == 'P2', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9813;
                                            </div>
                                        <div>
                                        <div class="player2Tool cl_d2_{!cell.cellIndexId}" style="{!IF(cell.status == 'P2', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9819;
                                            </div>
                                        </div>
                                    </apex:outputPanel>
                                    
                                    <apex:outputPanel rendered="{!isCurrentUser2}">
                                        <div onclick="cellClicked('2', {!cell.rowNumber}, {!cell.colNumber}); return false;" class="player2Tool cl_a2_{!cell.cellIndexId}" style="{!IF(cell.status == 'P2' && gameRecord.Player_Turn__c == 'P2', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9819;
                                            </div>
                                        </div>
                                        <div class="player2Tool cl_d2_{!cell.cellIndexId}" style="{!IF(cell.status == 'P2' && gameRecord.Player_Turn__c == 'P1', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9819;
                                            </div>
                                        </div>
                                        <div class="player1Tool cl_d1_{!cell.cellIndexId}" style="{!IF(cell.status == 'P1', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9813;
                                            </div>
                                        </div>
                                    </apex:outputPanel>
                                    
                                    
                                    <apex:outputPanel rendered="{!NOT isCurrentUser1 && NOT isCurrentUser2}">
                                        <div class="player1Tool cl_d1_{!cell.cellIndexId}" style="{!IF(cell.status == 'P1', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9813;
                                            </div>
                                        </div>
                                        <div class="player2Tool cl_d2_{!cell.cellIndexId}" style="{!IF(cell.status == 'P2', 'display:block;', 'display:none;')}">
                                            <div class="kingStyle" style="{!IF(cell.isKing, 'display;block;', 'display:none;')}">
                                                &#9819;
                                            </div>
                                        </div>
                                    </apex:outputPanel>
                                    
                                    <div onclick="optionalCellClicked('1', {!cell.rowNumber}, {!cell.colNumber}); return false;" class="optionalMoveP1 cl_p1_{!cell.cellIndexId}" style="display:none;" />
                                    <div onclick="optionalCellClicked('2', {!cell.rowNumber}, {!cell.colNumber}); return false;" class="optionalMoveP2 cl_p2_{!cell.cellIndexId}" style="display:none;" />
                                </td>
                            </apex:repeat>
                        </tr>
                    </apex:repeat>
                </table>
                
                <div class="playerImg">
                    <img class="{!IF(gameRecord.Player_Turn__c == 'P1', 'playerImgBorder', 'playerWaitImgBorder')}" src="{!IF(NOT isCurrentUser2, gameRecord.Player_1__r.SmallPhotoUrl, gameRecord.Player_2__r.SmallPhotoUrl)}" title="{!IF(NOT isCurrentUser2, gameRecord.Player_1__r.Name, gameRecord.Player_2__r.Name)}"/>
                </div>
            </div>
            
        </td>
        <td style="width:25%; vertical-align: top;">
            <div class="movementsPanel">
                <table cellspacing="0" cellpadding="0" style="min-width: 200px;">
                    <tr>
                        <th>No.</th>
                        <th>Player 1</th>
                        <th>Player 2</th>
                    </tr>
                    <apex:repeat value="{!l_movementItems}" var="moveItem">
                        <tr>
                            <td>{!moveItem.index}</td>
                            <td><apex:outputText value="{!moveItem.player1Move}" escape="false"/></td>
                            <td><apex:outputText value="{!moveItem.player2Move}" escape="false"/></td>
                        </tr>
                    </apex:repeat>
                </table>
            </div>
        </td>
    </tr>
</table>

<apex:actionFunction name="moveToolJS" action="{!moveTool}" reRender="formId,globalVars">
    <apex:param name="pnum" value=""/>
    <apex:param name="fcell" value=""/>
    <apex:param name="tcell" value=""/>
    <apex:param name="iseatmove" value=""/>
</apex:actionFunction>

<apex:actionFunction name="refreshBoardJS" action="{!refreshBoard}" reRender="formId,globalVars"/>

</apex:form>

</apex:page>


This is how the page looks while playing. 

Each player, on its turn can click his tools, clicking on tool will highlight the tools options, then clicking on highlight option will invoke the moveTool server method that will update the board, the game and switch the turn.



In the LWC version, most of the apex server logic remain similar, but I moved most of the init logic and the class variable to the JS controller. When the component is loading it invoke server method to get the Game data and then create the relevant structures.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import { LightningElement, api, track } from 'lwc';
import { subscribe, unsubscribe, onError, setDebugFlag, isEmpEnabled} from 'lightning/empApi';

import currentUserId from '@salesforce/user/Id';
import getGameData from '@salesforce/apex/PlayGameControllerLWC.getGameData';
import moveTool from '@salesforce/apex/PlayGameControllerLWC.moveTool';

export default class PlayCheckerGame extends LightningElement {

    @api recordId;
    @track gameRecord;
    @track isTurnUser1;
    @track isCurrentUserIsP1;
    @track isCurrentUserIsP2;
    @track isCurrentUserViewer;
    @track boardRows = [];
    @track allCellsMap;
    @track p1Tools;
    @track p2Tools;
    @track movementsLogs = [];

    @track gameTitle;

    @track mainUserCss;
    @track mainUserPhotoURL;
    @track mainUserName;

    @track opponentUserCss;
    @track opponentUserPhotoURL;
    @track opponentUserName;

    @track clickedToolRow;
    @track clickedToolCol;
    @track clickedOptions;

    connectedCallback(){
        getGameData({gameId: this.recordId}).then(
            result =>{
                this.gameRecord = result;

                this.loadGameData();
            }
        ).catch(error => {
                console.log('error' + error);
        });
    }

    loadGameData(){
        console.log('this.gameRecord :' + JSON.stringify(this.gameRecord));
        this.gameTitle = (this.gameRecord.Player_1__c == undefined ? '' : this.gameRecord.Player_1__r.Name) + ' VS ' + (this.gameRecord.Player_2__c == undefined ? '' : this.gameRecord.Player_2__r.Name);

        this.isTurnUser1 = this.gameRecord.Player_Turn__c == 'P1';
        this.isCurrentUserIsP1 = this.gameRecord.Player_1__c == currentUserId;
        this.isCurrentUserIsP2 = this.gameRecord.Player_2__c == currentUserId;

        this.isCurrentUserViewer = this.isCurrentUserIsP1 == false && this.isCurrentUserIsP2 == false;

        this.p1Tools = this.gameRecord.Player_1_Tools__c == undefined ? [] : JSON.parse(this.gameRecord.Player_1_Tools__c);
        this.p2Tools = this.gameRecord.Player_2_Tools__c == undefined ? [] : JSON.parse(this.gameRecord.Player_2_Tools__c);

        this.allCellsMap = {};
        this.boardRows = [];
        this.movementsLogs = [];

        if(this.gameRecord.Movement_Log__c != undefined){
            var allMovements = JSON.parse(this.gameRecord.Movement_Log__c);

            for(var moveIndex in allMovements){
                this.movementsLogs.push({
                    'index' : allMovements[moveIndex].index,
                    'player1Move' : allMovements[moveIndex].player1Move,
                    'player2Move': allMovements[moveIndex].player2Move
                });
            }
        }

        if(this.isCurrentUserIsP2 == false){
            
            this.mainUserName = this.gameRecord.Player_1__c == undefined ? '' : this.gameRecord.Player_1__r.Name;
            this.mainUserPhotoURL = this.gameRecord.Player_1__c == undefined ? '' : this.gameRecord.Player_1__r.SmallPhotoUrl;
            this.mainUserCss = (this.gameRecord.Player_Turn__c == 'P1') ? 'playerImgBorder' : '';
            this.opponentUserName = this.gameRecord.Player_2__c == undefined ? '' : this.gameRecord.Player_2__r.Name;
            this.opponentUserPhotoURL = this.gameRecord.Player_2__c == undefined ? '' : this.gameRecord.Player_2__r.SmallPhotoUrl;
            this.opponentUserCss = (this.gameRecord.Player_Turn__c == 'P2') ? 'playerImgBorder' : '';
        
            for(var rowIndex = 1; rowIndex <= 8; rowIndex++){
                var boardRow = {
                    'rowNumber' : rowIndex
                };
                var boardRowCells = [];

                for(var colIndex = 1; colIndex <=8; colIndex++){
                    var keyCell = rowIndex + '_' + colIndex;

                    var newCell = {
                        'keyCell' : keyCell,
                        'rowNumber' : rowIndex,
                        'colNumber' : colIndex,
                        'status' : (this.p1Tools[keyCell] != undefined ? 'P1' : (this.p2Tools[keyCell] != undefined ? 'P2' : 'E')),
                        'isPlayer1Status' : this.p1Tools[keyCell] != undefined,
                        'isPlayer2Status' : this.p2Tools[keyCell] != undefined,
                        'showPlayer1Option' : false,
                        'showPlayer2Option' : false,
                        'isEatOptionCell' : false,
                        'isEatPrev' : false,
                        'ignoreDirection' : '',
                        'classStlye' : ((colIndex + rowIndex) % 2 != 0 ? 'evenbg' : 'oddbg')
                    };
                    
                    newCell.isKing = (newCell.status == 'P1' && this.p1Tools[keyCell].toolType === 'K')
                        || (newCell.status == 'P2' && this.p2Tools[keyCell].toolType === 'K');

                    boardRowCells.push(newCell);
                    this.allCellsMap[keyCell] = newCell;
                }

                boardRow['rowCells'] = boardRowCells;
                this.boardRows.push(boardRow);
            }
        }
        else{ //user 2 
            this.mainUserName = this.gameRecord.Player_2__c == undefined ? '' : this.gameRecord.Player_2__r.Name;
            this.mainUserPhotoURL = this.gameRecord.Player_2__c == undefined ? '' : this.gameRecord.Player_2__r.SmallPhotoUrl;
            this.mainUserCss = (this.gameRecord.Player_Turn__c == 'P2') ? 'playerImgBorder' : '';
            this.opponentUserName = this.gameRecord.Player_1__c == undefined ? '' : this.gameRecord.Player_1__r.Name;
            this.opponentUserPhotoURL = this.gameRecord.Player_1__c == undefined ? '' : this.gameRecord.Player_1__r.SmallPhotoUrl;
            this.opponentUserCss = (this.gameRecord.Player_Turn__c == 'P1') ? 'playerImgBorder' : '';


            for(var rowIndex = 8; rowIndex >= 1; rowIndex--){
                var boardRow = {
                    'rowIndex' : rowIndex
                };
                var boardRowCells = [];

                for(var colIndex = 8; colIndex >=1; colIndex--){
                    var keyCell = rowIndex + '_' + colIndex;

                    var newCell = {
                        'keyCell' : keyCell,
                        'rowNumber' : rowIndex,
                        'colNumber' : colIndex,
                        'status' : (this.p1Tools[keyCell] != undefined ? 'P1' : (this.p2Tools[keyCell] != undefined ? 'P2' : 'E')),
                        'isPlayer1Status' : this.p1Tools[keyCell] != undefined,
                        'isPlayer2Status' : this.p2Tools[keyCell] != undefined,
                        'showPlayer1Option' : false,
                        'showPlayer2Option' : false,
                        'isEatOptionCell' : false,
                        'isEatPrev' : false,
                        'ignoreDirection' : '',
                        'classStlye' : ((colIndex + rowIndex) % 2 != 0 ? 'evenbg' : 'oddbg')
                    };
                    
                    newCell.isKing = (newCell.status == 'P1' && this.p1Tools[keyCell].toolType === 'K')
                        || (newCell.status == 'P2' && this.p2Tools[keyCell].toolType === 'K');

                    boardRowCells.push(newCell);
                    this.allCellsMap[keyCell] = newCell;
                }

                boardRow['rowCells'] = boardRowCells;
                this.boardRows.push(boardRow);
            }
        }

        
        //If it is not the player turn, listen to event
        if((this.isTurnUser1 === false && this.isCurrentUserIsP1 === true)
            || (this.isTurnUser1 === true && this.isCurrentUserIsP2 === true)
            || (this.isCurrentUserViewer)) {

            
			//Subscribe to event
			const gameEventHandler = (response) => {
                if(response.data.payload.GameId__c == this.recordId){
                    getGameData({gameId: this.recordId}).then(
                        result =>{
                            this.gameRecord = result;
                            this.loadGameData();
                        }
                    ).catch(error => {
                            console.log('error' + error);
                    });
                }
            }

            subscribe('/event/Game_Played__e', -1, gameEventHandler).then(response => {
                console.log('Successfully subscribed to : ', JSON.stringify(response.channel));
            });
            
                /*
            console.log('STARTING TIMER');
            //start timer
            var interval = setInterval(function(){
                
                var expectingNextTurn = this.isTurnUser1 ? 'P2' : 'P1';
                
                getGameData({gameId: this.recordId}).then(
                    result =>{
                        console.log('result TIMER: ' + result);

                        var updatedGame = result;

                        console.log('updatedGame.Player_Turn__c TIMER: ' + updatedGame.Player_Turn__c);

                        if(updatedGame.Player_Turn__c === expectingNextTurn) {
                            clearInterval(interval);

                            this.gameRecord = result;
                            this.loadGameData();
                        }
                    }
                ).catch(error => {
                        console.log('error' + error);
                });

            }.bind(this), 3000);*/
        }

    }
	
	cellClicked(event){    
        ...
    }

    optionalCellClicked(event){
		...
    }


    canEatMore(player, row, col){
        ...
    }
    
    canEatCell(player, otherPlayer, checkRow, checkCol, cellBeyondRow, cellBeyondCol){
        ...
    }

    isKingCanEat(playerNum, opponent, row, col, dirVert, dirHorz){
        ...
    }
	
    findKingOptions(player, row, col){
        ...
    }
}


In addition as can be seen in the code snippet, I commented the setInterval call and instead use platform event- - when user making a move it publish event and the component subscribe to this event. The event mechanism seems better and was quite easy. Technically could use it also in the visulaforce solution. After setup the event and create it with simple process builder, all is left to do is to subscribe from the component:

subscribe('/event/Game_Played__e', -1, gameEventHandler)


Next is the html code which in concept very similar to the visualforce page only with different syntax/elements.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<template>
    <template if:true={gameRecord}></template>
        <table style="width: auto;">
            <tr>
                <td style="width:75%">
                    <div class="boardstyle">
                        <div class="boardTitle">
                            {gameTitle}
                        </div>
                        
                        <div class="playerImg">
                            <img class={opponentUserCss} src={opponentUserPhotoURL} title={opponentUserName}/>    
                        </div>
                        <table>
                            <template for:each={boardRows} for:item='rowItem'>
                                <tr key={rowItem.rowNumber}>
                                    <template for:each={rowItem.rowCells} for:item='cellItem'>
                                        <td class={cellItem.classStlye} key={cellItem.keyCell}>
                                            <template if:true={isCurrentUserIsP1}>
                                                <template if:true={cellItem.isPlayer1Status}>
                                                    <template if:true={isTurnUser1}>
                                                        <div onclick={cellClicked} data-row={cellItem.rowNumber} data-col={cellItem.colNumber} data-player='1' class="player1Tool" style="cursor: pointer;">
                                                            <template if:true={cellItem.isKing}>
                                                                <div class="kingStyle" onclick={cellClicked} data-row={cellItem.rowNumber} data-col={cellItem.colNumber} data-player='1'>
                                                                    &#9813;
                                                                </div>
                                                            </template>
                                                        </div>
                                                    </template>

                                                    <template if:false={isTurnUser1}>
                                                        <div class="player1Tool">
                                                            <template if:true={cellItem.isKing}>
                                                                <div class="kingStyle">
                                                                    &#9813;
                                                                </div>
                                                            </template>
                                                        </div>
                                                    </template>
                                                </template>

                                                <template if:true={cellItem.isPlayer2Status}>
                                                    <div class="player2Tool">
                                                        <template if:true={cellItem.isKing}>
                                                            <div class="kingStyle">
                                                                &#9819;
                                                            </div>
                                                        </template>
                                                    </div>
                                                </template>
                                            </template>
                                            
                                            <template if:true={isCurrentUserIsP2}>

                                                <template if:true={cellItem.isPlayer2Status}>
                                                    <template if:false={isTurnUser1}>
                                                        <div onclick={cellClicked} data-row={cellItem.rowNumber} data-col={cellItem.colNumber} data-player='2' class="player2Tool" style="cursor: pointer;">
                                                            <template if:true={cellItem.isKing}>
                                                                <div class="kingStyle" onclick={cellClicked} data-row={cellItem.rowNumber} data-col={cellItem.colNumber} data-player='2'>
                                                                    &#9819;
                                                                </div>
                                                            </template>
                                                        </div>
                                                    </template>

                                                    <template if:true={isTurnUser1}>
                                                        <div class="player2Tool">
                                                            <template if:true={cellItem.isKing}>
                                                                <div class="kingStyle">
                                                                    &#9819;
                                                                </div>
                                                            </template>
                                                        </div>
                                                    </template>
                                                </template>

                                                <template if:true={cellItem.isPlayer1Status}>
                                                    <div class="player1Tool">
                                                        <template if:true={cellItem.isKing}>
                                                            <div class="kingStyle">
                                                                &#9813;
                                                            </div>
                                                        </template>
                                                    </div>
                                                </template>
                                            </template>
                                            
                                            <template if:true={isCurrentUserViewer}>
                                                <template if:true={cellItem.isPlayer1Status}>
                                                    <div class="player1Tool">
                                                        <template if:true={cellItem.isKing}>
                                                            <div class="kingStyle">
                                                                &#9813;
                                                            </div>
                                                        </template>
                                                    </div>
                                                </template>

                                                <template if:true={cellItem.isPlayer2Status}>
                                                    <div class="player2Tool">
                                                        <template if:true={cellItem.isKing}>
                                                            <div class="kingStyle">
                                                                &#9819;
                                                            </div>
                                                        </template>
                                                    </div>
                                                </template>
                                            </template>

                                            <template: if:true={cellItem.showPlayer1Option}>
                                                <div onclick={optionalCellClicked} data-row={cellItem.rowNumber} data-col={cellItem.colNumber} data-player='1' class='optionalMoveP1' style="cursor: pointer;"></div>
                                            </template:>
                                            <template: if:true={cellItem.showPlayer2Option}>
                                                <div onclick={optionalCellClicked} data-row={cellItem.rowNumber} data-col={cellItem.colNumber} data-player='2' class='optionalMoveP2' style="cursor: pointer;"></div>
                                            </template:>
                                       </td>
                                    </template>
                                </tr>
                            </template>
                        </table>
                        
                        <div class="playerImg">
                            <img class={mainUserCss} src={mainUserPhotoURL} title={mainUserName}/>    
                        </div>
                    </div>
                </td>
                
                <td style="width:25%; vertical-align: top;">
                    <div class="movementsPanel">
                        <table cellspacing="0" cellpadding="0" style="min-width: 200px;">
                            <tr>
                                <th>No.</th>
                                <th>Player 1</th>
                                <th>Player 2</th>
                            </tr>
                            <template for:each={movementsLogs} for:item='moveItem'>
                                <tr key={moveItem.index}>
                                    <td>{moveItem.index}</td>
                                    <td style="min-width: 100px;"><lightning-formatted-rich-text value={moveItem.player1Move}></lightning-formatted-rich-text></td>
                                    <td style="min-width: 100px;"><lightning-formatted-rich-text value={moveItem.player2Move}></lightning-formatted-rich-text></td>
                                </tr>
                            </template>
                        </table>
                    </div>
                </td>
            </tr>
        </table>
    </template>
</template>


From technical perspective I can highlight few remarks about the differences/complexity related to LWC vs visualforce.

1.Developing with the LWC structures lead you to divide the code correctly. Sometimes it seems annoying at first, but after further thinking and when viewing the final code you can see the benefits. For example, in LWC you cannot use operators in to render elements, only if:true/if:false this force you to write the condition logic inside the controller, where it should be.

In visualforce this is not being enforced. I can write complex conditions + CSS + JS in the page.

2.The development for Visualforce is still easier, as it have more suitable tools. In some cases we can also develop without any additional tools expect the browser. For LWC we must use external tool and push the changes any time we want to test our changes.

3.Lightning Components still have (and probably will have) caching issues. In some cases need to clear caching to get the process using with the latest changes. 





No comments:

Post a Comment

Retire of Permission on Profiles

If you are working as a Salesforce admin/developer you've probably heard somewhere that Salesforce is planning to make a significant cha...